PMO 2 Pertemuan 8 Firebase(Realtime Database, Firebase Cloud Messaging)

Firebase adalah suatu layanan dari Google yang digunakan untuk mempermudah para pengembang aplikasi dalam mengembangkan aplikasi. Dengan adanya Firebase, pengembang aplikasi bisa fokus mengembangkan aplikasi tanpa harus memberikan usaha yang besar. Dua fitur yang menarik dari Firebase yaitu Firebase Remote Config dan Firebase Realtime Database. Selain itu terdapat fitur pendukung untuk aplikasi yang membutuhkan pemberitahuan yaitu Firebase Notification.

Beberapa fitur yang dimiliki oleh Firebase adalah sebagai berikut :

  1. Firebase Analytics.
  2. Firebase Cloud Messaging dan Notifications
  3. Firebase Authentication.
  4. Firebase Remote Config.
  5. Firebase Real Time Database.
  6. Firebase Crash Reporting.

Dua fitur yang menarik adalah Firebase Remote Config dan Firebase Real Time Database. Secara sederhananya, Remote Config adalah fitur yang memungkinkan developer mengganti / mengubah beberapa konfigurasi aplikasi Android / iOS tanpa harus memberikan update aplikasi via Play Store / App Store. Salah satu konfigurasi yang bisa dimanipulasi adalah seperti warna / tema aplikasi.

Sedangkan Firebase Real Time Database adalah fitur yang memberikan sebuah NoSQL database yang bisa diakses secara Real Time oleh pengguna aplikasi. Dan hebatnya adalah aplikasi bisa menyimpan data secara lokal ketika tidak ada akses internet, kemudian melakukan sync data segera setelah mendapatkan akses internet.

  1. Firebase Analytic

Sebenarnya Analytics dari Firebase tidak jauh berbeda dengan Analytics yang digunakan oleh Google Analytics. Hanya saja Firebase menyediakan dashboard yang dirasa lebih sederhana dibandingkan dashboard yang digunakan oleh Google Analytics.

Firebase menawarkan fitur Analytics untuk keperluan koleksi data dan reporting untuk aplikasi Android / iOS. Koleksi data yang bisa digunakan pun bervariasi. Beberapa contohnya adalah kita bisa membuat report untuk pengguna aplikasi di negara Indonesia saja, atau negara Jepang saja, dll.

Selain itu kita bisa melihat fungsi / bagian mana dari aplikasi kita yang paling sering digunakan oleh user. Salah satu hal yang paling menarik dari Analytics ini adalah kita bisa membuat segmentasi user berdasarkan ‘user attribute’. User attribute ini adalah parameter yang bisa kita gunakan sebagai filter untuk reporting dan notifikasi. Contoh sederhananya diibaratkan untuk aplikasi online shop. Dengan ‘user attribute’, kita bisa mencari tahu berapa jumlah user yang membeli handphone dengan merk ‘A’, atau berapa jumlah user yang membeli sepeda. Atau kita bahkan bisa mencari tahu pada jam berapa paling banyak terjadi transaksi yang dilakukan user.

Nah dengan data-data ini kita juga bisa memberikan notifikasi kepada segmentasi user yang kita inginkan, contohnya kita hanya ingin mengirimkan notifikasi untuk user yang telah membeli handphone dengan merk ‘A’. Selain itu masih ada banyak fitur-fitur dari Analytics yang ditawarkan oleh Firebase.

  1. Firebase Cloud Messaging dan Notifications.

Firebase Cloud Messaging / FCM, adalah layanan yang diberikan oleh Firebase untuk menggantikan Google Cloud Messaging (GCM). Pihak Google menyarankan untuk aplikasi yang masih menggunakan GCM untuk segera migrasi ke FCM.

Fitur-fitur yang diberikan oleh GCM sebenarnya tidak terlalu jauh berbeda dengan GCM. Dengan FCM kita bisa memberikan push notification dan membuat komunikasi dua arah antara device. Teknologi yang digunakan terbagi menjadi dua :

  • XMPP (Extensible Messaging and Presence Protocol)
  • HTTP (Hypertext Transfer Protocol).

Untuk XMPP kita harus membangun server XMPP terlebih dahulu, sedangkan untuk HTTP kita bisa menggunakan console yang disediakan oleh Firebase. Kita juga bisa mengatur push notification yang ingin kita kirim, contohnya kita hanya ingin mengirimkan notifikasi kepada segmentasi user / audience yang membeli handphone dengan merk ‘A’. Atau kita ingin mengirimkan notifikasi hanya kepada user yang menggunakan aplikasi dengan versi 1.0. Selain itu ada juga fitur untuk mengirimkan notifikasi berdasarkan ‘topic’. Fitur ini mirip dengan ‘user attribute’, tetapi dengan ‘topic’, user bisa melakukan kegiatan ‘subscribe’ dan ‘unsubscribe’.

  1. Firebase Authentication

Firebase authentication adalah layanan yang diberikan oleh Firebase untuk fungsi user membership. Fitur-fitur yang diberikan adalah register / login dengan beberapa metode :
Alamat email dan password.
Akun Google.
Akun Facebook.
Akun Twitter.
Akun GitHub.
Akun Anonymous.

Fitur yang menarik adalah fitur login dengan akun anonymous. Contohnya adalah user bisa melakukan login secara sementara ketika melihat-lihat barang di aplikasi online shop kita. Nah ketika user ingin melakukan pembelian, maka user tersebut diharuskan untuk login dengan salah satu metode yang didukung Firebase (email-password, Google, Facebook, Twitter, GitHub). Setelah user tersebut login, kita bisa mengumpulkan data-data yang dilihat oleh user tersebut ketika masih menggunakan akun anonymous, kemudian menggabungkannya dengan user yang sekarang telah login dengan salah satu metode login yang dipilih.

Salah satu tujuan penggunaan akun anonymous adalah supaya kita tidak perlu memaksa user untuk login, sebelum user tersebut ingin melakukan transaksi.

  1. Firebase Remote Config.

Remote Config adalah fitur yang memungkinkan kita untuk melakukan perubahan konfigurasi di dalam aplikasi Android / iOS, tanpa harus melakukan update aplikasi di Play Store / App Store. Salah satu contohnya adalah ketika hari Natal, kita bisa mengganti tema aplikasi dengan warna merah-putih. Atau ketika hari tahun baru, kita bisa mengganti tema aplikasi dengan warna hijau-silver, dll.

Cara kerja dari Remote Config adalah aplikasi menyimpan terlebih dahulu file XML yang berisi parameter-parameter yang nilainya akan bisa diganti melalui console Firebase. Kemudian objek firebase di dalam aplikasi akan melakukan request data dari server, kemudian me’load data-data tersebut. Secara default, objek firebase di dalam aplikasi akan melakukan request 12 jam / 1x, tetapi kita bisa mengubahnya bila kita inginkan.

Salah satu catatan dari Google adalah kita tidak boleh menggunakan Remote Config untuk melakukan perubahan yang krusial, seperti mengubah permission yang dibutuhkan oleh aplikasi.

  1. Firebase Real Time Database.

Real Time Database adalah sebuah NoSQL database yang disediakan oleh Firebase. NoSQL database adalah database yang tidak menggunakan sistem relasi layaknya pada database tradisional (MySQL dll.). Metode penyimpanan data di dalam NoSQL menggunakan objek yang menggunakan format JSON (JavaScript Object Notation).

Firebase memungkinkan kita untuk menggunakan NoSQL database yang di’share kepada semua user, dan ketika terjadi perubahan data pada database tersebut, user akan segera mendapatkan update data secara real time. Tetapi bukan berarti database ini tidak mempunyai unsur keamanan, karena kita bisa mengatur hak akses yang berbeda untuk setiap user.

Salah satu fitur yang menarik adalah aplikasi bisa menyimpan data secara lokal ketika tidak ada koneksi internet, kemudian melakukan sync data segera setelah mendapatkan kembali koneksi internet. Real Time database ini sepertinya cocok untuk aplikasi instant messaging.

  1. Firebase Crash Reporting

Crash Reporting adalah layanan yang diberikan oleh Firebase untuk keperluan merekam setiap exception yang terjadi pada aplikasi. Report yang diberikan cukup detail dengan beberapa filter seperti yang digunakan pada Analytics. Crash reporting ini juga dibagi menjadi dua bagian :
– Non Fatal exception, untuk exception yang tidak berdampak fatal (kita bisa membuat custom exception).
– Fatal exception, untuk exception yang fatal (aplikasi crash).

And that’s it kira-kira pengenalan beberapa fitur pada Firebase. Sebenarnya masih ada beberapa fitur lain yang belum dibahas di postingan kali ini, seperti Firebase App Indexing untuk men’indeks aplikasi kita pada pencarian Google. Dan Firebase Dynamic Links untuk membuat link untuk aplikasi kita, dan dengan link tersebut user akan bisa langsung masuk ke bagian yang dituju oleh link tersebut (tanpa harus masuk ke halaman awal aplikasi).

Firebase Realtime Database

Firebase Realtime Database adalah database yang di-host di cloud. Data disimpan sebagai JSON dan disinkronkan secara realtime ke setiap klien yang terhubung. Ketika Anda membuat aplikasi lintas-platform dengan SDK Android, iOS, dan JavaScript, semua klien akan berbagi sebuah instance Realtime Database dan menerima update data terbaru secara otomatis.

Kemampuan utama

-Realtime

-Offline

-Dapat Diakses dari Perangkat Klien

Bagaimana cara kerjanya?

Firebase Realtime Database memungkinkan Anda untuk membuat aplikasi kolaboratif dan kaya fitur dengan menyediakan akses yang aman ke database, langsung dari kode sisi klien. Data disimpan di drive lokal. Bahkan saat offline sekalipun, peristiwa realtime terus berlangsung, sehingga pengguna akhir akan merasakan pengalaman yang responsif. Ketika koneksi perangkat pulih kembali, Realtime Database akan menyinkronkan perubahan data lokal dengan update jarak jauh yang terjadi selama klien offline, sehingga setiap perbedaan akan otomatis digabungkan.

Realtime Database menyediakan bahasa aturan berbasis ekspresi yang fleksibel, atau disebut juga Aturan Keamanan Firebase Realtime Database, untuk menentukan metode strukturisasi data dan kapan data dapat dibaca atau ditulis. Ketika diintegrasikan dengan Firebase Authentication, developer dapat menentukan siapa yang memiliki akses ke data tertentu dan bagaimana mereka dapat mengaksesnya.

Realtime Database adalah database NoSQL, sehingga memiliki pengoptimalan dan fungsionalitas yang berbeda dengan database terkait. API Realtime Database dirancang agar hanya mengizinkan operasi yang dapat dijalankan dengan cepat. Hal ini memungkinkan Anda untuk membangun pengalaman realtime yang luar biasa dan dapat melayani jutaan pengguna tanpa mengorbankan kemampuan respons.

 

 

Mendaftarkan SHA 1 pada firebase

Buka Firebase Konsol

Pilih New Projek dan masukkan nama projek

Klik Buat proyek Masukkan Nama package,nama aplikasi, dan kode SHA 1

Buka Android Studio

Dan lihatlah di sebalah kanan ada panel Gradle. mungkin anda baru sadar karena tidak pernah memakainya. Seperti saya 😀 Nah, sekarang coba

Cari di app>Tasks > android > signinReport. Klik 2 kali

Maka data tentang MD5, SHA1, dan valid time akan muncul pada panel Run

P.s : SHA di atas adalah untuk mode debug saja.

Untuk Mode Release :

  1. Build > Generate Signed APK
  2. Copy “Keystore Path” dan “KeyAlias”
  3. Masuk ke folder bin di Jdk ex :
  4. Buka Terminal CMD
  5. Masuk ke folder bin di JDK
    cd C:\Program Files\Java\jdk1.8.0_91\bin
  6. Enter ini dan masukkan password
    keytool -list -v -keystore “Keystore path” -alias “Key Alias”
  7. Masukkan Password dan akan muncul kode-SHA

 

=================================================================

PROJEK SEDERHANA 

Untuk Membuat sebuah keajaiban J Kita persiapkan dulu :

◉Android Studio

◉Library Butter Knife, Glide,RecyclerView,Firebase

◉Firebase functions push notifications

◉Realtime database

◉Firebase Functions

Gradle

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    implementation 'com.android.support:appcompat-v7:26.0.0'
    implementation 'com.android.support:cardview-v7:26.0.0'
    implementation 'com.android.support:design:26.0.0'
    implementation 'com.android.support:support-v4:26.0.0'
    implementation 'com.google.firebase:firebase-auth:10.2.0'
    implementation 'com.google.firebase:firebase-database:10.2.0'
    implementation 'com.google.firebase:firebase-messaging:10.2.0'
    implementation 'com.firebaseui:firebase-ui-database:1.2.0'
    implementation 'com.google.android.gms:play-services-auth:10.2.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation 'com.github.bumptech.glide:glide:3.7.0'
    implementation 'jp.wasabeef:glide-transformations:2.0.1'
    implementation 'frankiesardo:icepick:3.2.0'

    implementation 'org.greenrobot:eventbus:3.0.0'
    implementation 'com.jakewharton:butterknife:8.4.0'
    implementation 'com.zsoltsafrany:needle:1.0.0'
    testImplementation 'junit:junit:4.12'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
    //compileOnly 'frankiesardo:icepick-processor:3.2.0'
    implementation 'joda-time:joda-time:2.9.7'
}

Login Activity.java

package afterapps.com.Trian.login;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.auth.api.signin.GoogleSignInResult;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GoogleAuthProvider;
import com.google.firebase.iid.FirebaseInstanceId;

import java.io.IOException;

import afterapps.com.Trian.R;
import afterapps.com.Trian.beans.User;
import afterapps.com.Trian.home.MainActivity;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import icepick.Icepick;
import icepick.State;
import needle.Needle;

import static android.view.View.GONE;
import static android.view.View.VISIBLE;

/*
 * Author Trian on 8/12/2018.
 */

public class LoginActivity extends AppCompatActivity {
    private static final int RC_SIGN_IN = 1;
    @BindView(R.id.activity_login_google_button)
    SignInButton googleButton;
    @BindView(R.id.activity_login_progress)
    ProgressBar progress;
    private GoogleApiClient mGoogleApiClient;
    private FirebaseAuth.AuthStateListener mAuthListener;
    private FirebaseAuth mAuth;

    @State
    boolean isLoading;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        ButterKnife.bind(this);
        Icepick.restoreInstanceState(this, savedInstanceState);

        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestEmail()
                .requestIdToken(getString(R.string.default_web_client_id))
                .build();

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build();

        mAuth = FirebaseAuth.getInstance();

        mAuthListener = new FirebaseAuth.AuthStateListener() {
            @Override
            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
                FirebaseUser user = firebaseAuth.getCurrentUser();
                if (user != null) {
                    // TODO User sedang login
                    Log.d("@@@@", "onAuthStateChanged:signed_in:" + user.getUid());
                    User use = new User();
                    use.setCurrent(user.getDisplayName().toString());
                    Intent home = new Intent(LoginActivity.this, MainActivity.class);
                    startActivity(home);
                    finish();
                } else {
                    // TODO User sedang logout
                    Log.d("@@@@", "onAuthStateChanged:signed_out");
                }
            }
        };
        displayLoadingState();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Icepick.saveInstanceState(this, outState);
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mAuthListener != null) {
            mAuth.removeAuthStateListener(mAuthListener);
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        if (mAuthListener != null) {
            mAuth.addAuthStateListener(mAuthListener);
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
        if (requestCode == RC_SIGN_IN) {
            hideProgress();
            GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
            handleGoogleSignInResult(result);
        }
    }

    private void hideProgress() {
        isLoading = false;
        displayLoadingState();
    }

    private void displayLoadingState() {
        progress.setVisibility(isLoading ? VISIBLE : GONE);
        googleButton.setVisibility(!isLoading ? VISIBLE : GONE);
    }

    private void showProgress() {
        isLoading = true;
        displayLoadingState();
    }

    private void handleGoogleSignInResult(GoogleSignInResult result) {
        if (result.isSuccess()) {
            // Google Sign In was successful, authenticate with Firebase
            GoogleSignInAccount account = result.getSignInAccount();
            firebaseAuthWithGoogle(account);
        } else {
            Toast.makeText(this, R.string.error_google_sign_in, Toast.LENGTH_SHORT).show();
        }
    }

    private void firebaseAuthWithGoogle(GoogleSignInAccount acct) {
        Log.d("@@@@", "firebaseAuthWithGoogle:" + acct.getId());

        AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
        mAuth.signInWithCredential(credential)
                .addOnCompleteListener(this, new OnCompleteListener() {
                    @Override
                    public void onComplete(@NonNull Task task) {
                        Log.d("@@@@", "firebaseAuthWithGoogleComplete:" + task.isSuccessful());

                        Needle.onBackgroundThread().execute(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    FirebaseInstanceId.getInstance().deleteInstanceId();
                                } catch (IOException e) {
                                    Log.d("@@@@", "deleteInstanceIdCatch: " + e.getMessage());
                                }
                            }
                        });

                        if (!task.isSuccessful()) {
                            Log.w("@@@@", "firebaseAuthWithGoogleFailed", task.getException());
                            Toast.makeText(LoginActivity.this, R.string.error_google_sign_in, Toast.LENGTH_SHORT).show();
                        }
                    }
                });
    }

    @OnClick(R.id.activity_login_google_button)
    public void attemptGoogleSignIn() {
        Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
        startActivityForResult(signInIntent, RC_SIGN_IN);
        showProgress();
    }
}

Login Activity.xml

 

 

 

 

 

Chat Activity.java

 

package afterapps.com.Trian.thread;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TextInputEditText;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;
import com.google.firebase.database.ValueEventListener;

import java.util.Calendar;
import java.util.Date;

import afterapps.com.Trian.BaseActivity;
import afterapps.com.Trian.Constants;
import afterapps.com.Trian.R;
import afterapps.com.Trian.beans.Message;
import afterapps.com.Trian.beans.User;
import afterapps.com.Trian.login.LoginActivity;
import afterapps.com.Trian.widgets.EmptyStateRecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import icepick.Icepick;
import icepick.State;

import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;

/*
 * Author Trian on 8/12/2018.
 */

public class ThreadActivity extends BaseActivity implements TextWatcher {
    //TODO  variable with butter knife
    @BindView(R.id.activity_thread_toolbar)
    Toolbar toolbar;
    @BindView(R.id.activity_thread_messages_recycler)
    EmptyStateRecyclerView messagesRecycler;
    @BindView(R.id.activity_thread_send_fab)
    FloatingActionButton sendFab;
    @BindView(R.id.activity_thread_input_edit_text)
    TextInputEditText inputEditText;
    @BindView(R.id.activity_thread_empty_view)
    TextView emptyView;
    @BindView(R.id.activity_thread_editor_parent)
    RelativeLayout editorParent;
    @BindView(R.id.activity_thread_progress)
    ProgressBar progress;

    private DatabaseReference mDatabase;
    private FirebaseAuth mAuth;
    private FirebaseAuth.AuthStateListener mAuthListener;

    @State
    String userUid;
    @State
    boolean emptyInput;

    private User user;
    private FirebaseUser owner;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread);

        Icepick.restoreInstanceState(this, savedInstanceState);
        ButterKnife.bind(this);
        setSupportActionBar(toolbar);

        mDatabase = FirebaseDatabase.getInstance().getReference();

        if (savedInstanceState == null) {
            userUid = getIntent().getStringExtra(Constants.USER_ID_EXTRA);
        }
        sendFab.requestFocus();

        loadUser();
        initializeAuthListener();
        initializeInteractionListeners();
    }
    private void initializeInteractionListeners() {
        inputEditText.addTextChangedListener(this);
    }
    //TODO user detail
    private void loadUser() {

        DatabaseReference userReference = mDatabase
                .child("users")
                .child(userUid);

        userReference.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                //TODO 
                user = dataSnapshot.getValue(User.class);
                initializeMessagesRecycler();
                displayUserDetails();
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
                Toast.makeText(ThreadActivity.this, R.string.error_loading_user, Toast.LENGTH_SHORT).show();
                finish();
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mAuthListener != null) {
            mAuth.removeAuthStateListener(mAuthListener);
        }
    }

    private void initializeAuthListener() {
        mAuth = FirebaseAuth.getInstance();
        mAuthListener = new FirebaseAuth.AuthStateListener() {
            @Override
            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
                owner = firebaseAuth.getCurrentUser();
                if (owner != null) {
                    initializeMessagesRecycler();

                    Log.d("@@@@", "thread:signed_in:" + owner.getUid());
                } else {
                    Log.d("@@@@", "thread:signed_out");
                    Intent login = new Intent(ThreadActivity.this, LoginActivity.class);
                    startActivity(login);
                    finish();
                }
            }
        };
        mAuth.addAuthStateListener(mAuthListener);
    }

    private void initializeMessagesRecycler() {
        if (user == null || owner == null) {
            Log.d("@@@@", "initializeMessagesRecycler: User:" + user + " Owner:" + owner);
            return;
        }
        //TODO : nah get data from firebase realtime database
        Query messagesQuery = mDatabase
                .child("messages")
                .child(owner.getUid())
                .child(user.getUid())
                .orderByChild("negatedTimestamp");
        //TODO : set adapter
        MessagesAdapter adapter = new MessagesAdapter(this, owner.getUid(), messagesQuery);

        //TODO attach adatper ke recyclerview
        messagesRecycler.setAdapter(null);
        messagesRecycler.setAdapter(adapter);
        messagesRecycler.setLayoutManager(
                new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true));
        messagesRecycler.setEmptyView(emptyView);
        messagesRecycler.getAdapter().registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
            @Override
            public void onItemRangeInserted(int positionStart, int itemCount) {
                super.onItemRangeInserted(positionStart, itemCount);
                messagesRecycler.smoothScrollToPosition(0);
            }
        });
    }

    //TODO : method onclick melalui butterknife
    @OnClick(R.id.activity_thread_send_fab)
    public void onClick() {
        if (user == null || owner == null) {
            Log.d("@@@@", "onSendClick: User:" + user + " Owner:" + owner);
            return; }
        long timestamp = new Date().getTime();
        long dayTimestamp = getDayTimestamp(timestamp);
        String body = inputEditText.getText().toString().trim();
        String ownerUid = owner.getUid();
        String userUid = user.getUid();
        Message message =
                new Message(timestamp, -timestamp, dayTimestamp, body, ownerUid, userUid);
        mDatabase
                .child("notifications")
                .child("messages")
                .push()
                .setValue(message);
        mDatabase
                .child("messages")
                .child(userUid)
                .child(ownerUid)
                .push()
                .setValue(message);
        if (!userUid.equals(ownerUid)) {
            mDatabase
                    .child("messages")
                    .child(ownerUid)
                    .child(userUid)
                    .push()
                    .setValue(message);
        }
        inputEditText.setText("");
    }

    //TODO ga guna pfft
    @Override
    protected void displayLoadingState() {
        //nampilkan loading tapi firebase offline bikin ga guna

        //TransitionManager.beginDelayedTransition(editorParent);
        progress.setVisibility(isLoading ? VISIBLE : INVISIBLE);
        //displayInputState();
    }

    private void displayInputState() {
        //inputEditText.setEnabled(!isLoading);
        sendFab.setEnabled(!emptyInput && !isLoading);
        //sendFab.setImageResource(isLoading ? R.color.colorTransparent : R.drawable.ic_send);
    }

    //TODO method set format ambil tanggal
    private long getDayTimestamp(long timestamp) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(timestamp);
        calendar.set(Calendar.MILLISECOND, 0);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MINUTE, 0);
        return calendar.getTimeInMillis();
    }

    private void displayUserDetails() {
        //todo[improvement]: maybe display the picture in the toolbar.. WhatsApp style
        toolbar.setTitle(user.getDisplayName());
        toolbar.setSubtitle(user.getEmail());
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {
        emptyInput = s.toString().trim().isEmpty();
        displayInputState();
    }
}

Chat Layout.xml

 

 

 

 

 

Chat Adapter.java

package afterapps.com.Trian.thread;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.support.transition.TransitionManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.firebase.ui.database.FirebaseRecyclerAdapter;
import com.google.firebase.database.Query;

import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.Interval;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.util.ArrayList;

import afterapps.com.Trian.R;
import afterapps.com.Trian.beans.Message;
import butterknife.BindView;
import butterknife.ButterKnife;

import static android.content.Context.CLIPBOARD_SERVICE;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;

/*
 * Author Trian on 8/12/2018.
 */

class MessagesAdapter extends FirebaseRecyclerAdapter {
    private static final int VIEW_TYPE_SENT = 0;
    private static final int VIEW_TYPE_SENT_WITH_DATE = 1;
    private static final int VIEW_TYPE_RECEIVED = 2;
    private static final int VIEW_TYPE_RECEIVED_WITH_DATE = 3;
    private final String ownerUid;
    private final Context context;
    private ArrayList selectedPositions;
    MessagesAdapter(Context context, String ownerUid, Query ref) {
        super(Message.class, R.layout.item_message_sent, MessageViewHolder.class, ref);
        this.context = context;
        this.ownerUid = ownerUid;
        selectedPositions = new ArrayList();
    }
    @Override
    protected void populateViewHolder(MessageViewHolder holder, Message message, int position) {
        holder.setMessage(message);
    }
    @Override
    public int getItemViewType(int position) {
        Message message = getItem(position);
        if (message.getFrom().equals(ownerUid)) {
            if (position == getItemCount() - 1 || selectedPositions.contains(position) ||
                    getItem(position + 1).getDayTimestamp() != message.getDayTimestamp()) {
                return VIEW_TYPE_SENT_WITH_DATE;
            } else {
                return VIEW_TYPE_SENT;
            }
        } else {
            if (position == getItemCount() - 1 || selectedPositions.contains(position) ||
                    getItem(position + 1).getDayTimestamp() != message.getDayTimestamp()) {
                return VIEW_TYPE_RECEIVED_WITH_DATE;
            } else {
                return VIEW_TYPE_RECEIVED;
            }
        }
    }

    @Override
    public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView;
        switch (viewType) {
            case VIEW_TYPE_SENT:
                itemView = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.item_message_sent, parent, false);
                break;
            case VIEW_TYPE_SENT_WITH_DATE:
                itemView = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.item_message_sent, parent, false);
                break;
            case VIEW_TYPE_RECEIVED:
                itemView = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.item_message_received, parent, false);
                break;
            case VIEW_TYPE_RECEIVED_WITH_DATE:
                itemView = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.item_message_received, parent, false);
                break;
            default:
                itemView = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.item_message_sent, parent, false);
        }
        return new MessageViewHolder(itemView);
    }

    class MessageViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {

        @BindView(R.id.item_message_date_text_view)
        TextView itemMessageDateTextView;
        @BindView(R.id.item_message_body_text_view)
        TextView itemMessageBodyTextView;
        @BindView(R.id.item_message_parent)
        LinearLayout itemMessageParent;

        MessageViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
            itemMessageBodyTextView.setOnClickListener(this);
            itemMessageBodyTextView.setOnLongClickListener(this);
        }

        void setMessage(Message message) {
            int viewType = MessagesAdapter.this.getItemViewType(getLayoutPosition());
            itemMessageBodyTextView.setText(message.getBody());
            boolean shouldHideDate = viewType == VIEW_TYPE_SENT || viewType == VIEW_TYPE_RECEIVED;
            itemMessageDateTextView.setVisibility(shouldHideDate ? GONE : VISIBLE);
            if (!shouldHideDate) {
                itemMessageDateTextView.setText(getDatePretty(message.getTimestamp(), true));
            }
        }

        @Override
        public void onClick(View v) {
            //if (selectedPositions.contains(getLayoutPosition())) {
            //    selectedPositions.remove(Integer.valueOf(getLayoutPosition()));
            //    setDateVisibility(GONE);
            //} else {
            //    selectedPositions.add(getLayoutPosition());
            //    setDateVisibility(VISIBLE);
            //}
        }

        private void setDateVisibility(int visibility) {
            TransitionManager.beginDelayedTransition(itemMessageParent);
            itemMessageDateTextView.setVisibility(visibility);
        }

        @Override
        public boolean onLongClick(View v) {
            Message message = getItem(getLayoutPosition());
            if (message != null) {
                ClipboardManager clipboard = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);
                ClipData clip = ClipData.newPlainText(
                        context.getString(R.string.clipboard_title_copied_message),
                        message.getBody());
                clipboard.setPrimaryClip(clip);
                Toast.makeText(context, R.string.message_message_copied, Toast.LENGTH_SHORT).show();
            }
            return true;
        }
    }

    private String getDatePretty(long timestamp, boolean showTimeOfDay) {
        DateTime yesterdayDT = new DateTime(DateTime.now().getMillis() - 1000 * 60 * 60 * 24);
        yesterdayDT = yesterdayDT.withTime(0, 0, 0, 0);
        Interval today = new Interval(DateTime.now().withTimeAtStartOfDay(), Days.ONE);
        Interval yesterday = new Interval(yesterdayDT, Days.ONE);
        DateTimeFormatter timeFormatter = DateTimeFormat.shortTime();
        DateTimeFormatter dateFormatter = DateTimeFormat.shortDate();
        if (today.contains(timestamp)) {
            if (showTimeOfDay) {
                return timeFormatter.print(timestamp);
            } else {
                return context.getString(R.string.today);
            }
        } else if (yesterday.contains(timestamp)) {
            return context.getString(R.string.yesterday);
        } else {
            return dateFormatter.print(timestamp);
        }
    }
}

Chat Model.java

package afterapps.com.Trian.beans;

/*
 * Author Trian on 8/12/2018.
 */

public class Message {
    private long timestamp;
    private long negatedTimestamp;
    private long dayTimestamp;
    private String body;
    private String from;
    private String to;
    public Message(long timestamp, long negatedTimestamp, long dayTimestamp, String body, String from, String to) {
        this.timestamp = timestamp;
        this.negatedTimestamp = negatedTimestamp;
        this.dayTimestamp = dayTimestamp;
        this.body = body;
        this.from = from;
        this.to = to;
    }
    public Message() {
    }

    public long getTimestamp() {
        return timestamp;
    }

    public long getNegatedTimestamp() {
        return negatedTimestamp;
    }

    public String getTo() {
        return to;
    }

    public long getDayTimestamp() {
        return dayTimestamp;
    }

    public String getFrom() {
        return from;
    }

    public String getBody() {
        return body;
    }
}

User Activity.java

package afterapps.com.Trian.home;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.widget.TextView;

import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.iid.FirebaseInstanceId;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import afterapps.com.Trian.Constants;
import afterapps.com.Trian.R;
import afterapps.com.Trian.beans.User;
import afterapps.com.Trian.login.LoginActivity;
import afterapps.com.Trian.thread.ThreadActivity;
import afterapps.com.Trian.widgets.EmptyStateRecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
/*
 * Author Trian on 8/12/2018.
 */
public class MainActivity extends AppCompatActivity {

    @BindView(R.id.activity_main_toolbar)
    Toolbar toolbar;
    @BindView(R.id.activity_main_users_recycler)
    EmptyStateRecyclerView usersRecycler;
    @BindView(R.id.activity_main_empty_view)
    TextView emptyView;
    private FirebaseAuth mAuth;
    private FirebaseAuth.AuthStateListener mAuthListener;
    private DatabaseReference mDatabase;
    @Override
    public void onStart() {
        super.onStart();
        EventBus.getDefault().register(this);
        mAuth.addAuthStateListener(mAuthListener);
    }
    @Override
    public void onStop() {
        super.onStop();
        EventBus.getDefault().unregister(this);
        if (mAuthListener != null) {
            mAuth.removeAuthStateListener(mAuthListener);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        setSupportActionBar(toolbar);

        mDatabase = FirebaseDatabase.getInstance().getReference();

        initializeFirebaseAuthListener();
        initializeUsersRecycler();
    }
    private void initializeUsersRecycler() {
        UsersAdapter adapter = new UsersAdapter(this, mDatabase.child("users"));
        usersRecycler.setAdapter(adapter);
        usersRecycler.setLayoutManager(new LinearLayoutManager(this));
        usersRecycler.setEmptyView(emptyView);
    }
    private void initializeFirebaseAuthListener() {
        mAuth = FirebaseAuth.getInstance();
        mAuthListener = new FirebaseAuth.AuthStateListener() {
            @Override
            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
                FirebaseUser user = firebaseAuth.getCurrentUser();
                User us = new User();
                if (user != null) {
                    addUserToDatabase(user);
                    Log.d("@@@@", "home:signed_in:" + user.getUid());
                } else {
                    Log.d("@@@@", "home:signed_out");
                    Intent login = new Intent(MainActivity.this, LoginActivity.class);
                    startActivity(login);
                    finish();
                }
            }
        };
    }

    private void addUserToDatabase(FirebaseUser firebaseUser) {
        User user = new User(
                firebaseUser.getDisplayName(),
                firebaseUser.getEmail(),
                firebaseUser.getUid(),
                firebaseUser.getPhotoUrl() == null ? "" : firebaseUser.getPhotoUrl().toString()
        );

        mDatabase.child("users")
                .child(user.getUid()).setValue(user);

        String instanceId = FirebaseInstanceId.getInstance().getToken();
        if (instanceId != null) {
            mDatabase.child("users")
                    .child(firebaseUser.getUid())
                    .child("instanceId")
                    .setValue(instanceId);
        }
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onUserSelected(DatabaseReference selectedRef) {
        Intent thread = new Intent(this, ThreadActivity.class);
        thread.putExtra(Constants.USER_ID_EXTRA, selectedRef.getKey());
        startActivity(thread);
    }
}

User Layout.xml

 

 

 

 

 

User Adapter.java

package afterapps.com.Trian.home;

import android.app.Dialog;
import android.content.Context;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.firebase.ui.database.FirebaseRecyclerAdapter;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.database.Query;

import org.greenrobot.eventbus.EventBus;

import afterapps.com.Trian.R;
import afterapps.com.Trian.beans.User;
import butterknife.BindView;
import butterknife.ButterKnife;
import jp.wasabeef.glide.transformations.CropCircleTransformation;

/*
 * Author Trian on 8/12/2018.
 */

class UsersAdapter extends FirebaseRecyclerAdapter {
    private final Context context;
    FirebaseAuth firebaseAuth;
    UsersAdapter(Context context, Query ref) {
        super(User.class, R.layout.item_user, UserViewHolder.class, ref);
        this.context = context;
    }
    @Override
    protected void populateViewHolder(UserViewHolder holder, User user, int position) {
        holder.setUser(user);
    }
    @Override
    public UserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_user, parent, false);

        return new UserViewHolder(itemView);
    }
    class UserViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
        @BindView(R.id.item_user_image_view)
        ImageView itemUserImageView;
        @BindView(R.id.item_friend_name_text_view)
        TextView itemFriendNameTextView;
        @BindView(R.id.item_friend_email_text_view)
        TextView itemFriendEmailTextView;
        @BindView(R.id.item_user_parent)
        CardView itemUserParent;
        UserViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
            itemUserParent.setOnClickListener(this);


            itemUserImageView.setOnClickListener(this);


        }

        @Override
        public void onClick(View v) {
            switch (v.getId()) {


                case R.id.item_user_parent:
                    EventBus.getDefault().post(getRef(getLayoutPosition()));
                    break;
                case R.id.item_user_image_view:


                    break;
            }
        }

        void setUser(User user) {

           if (!user.getDisplayName().equals(user.getCurrent())){
               itemFriendNameTextView.setText(user.getDisplayName());
               itemFriendEmailTextView.setText(user.getEmail());
               Glide.with(context)
                       .load(user.getPhotoUrl())
                       .placeholder(R.drawable.placeholder_user)
                       .centerCrop()
                       .dontAnimate()
                       .bitmapTransform(new CropCircleTransformation(context))
                       .into(itemUserImageView);
            }

        }
    }
}

User Model.java

package afterapps.com.Trian.beans;

/*
 * Author Trian on 8/12/2018.
 */

public class User {
    private String displayName;
    private String current;
    private String email;
    private String uid;
    private String photoUrl;
    private String instanceId;
    public User() {
    }
    public User(String displayName, String email, String uid, String photoUrl) {
        this.displayName = displayName;
        this.email = email;
        this.uid = uid;
        this.photoUrl = photoUrl;
    }
    public String getCurrent() {
        return current;
    }

    public void setCurrent(String current) {
        this.current = current;
    }

    public String getInstanceId() {
        return instanceId;
    }

    public String getDisplayName() {
        return displayName;
    }

    public String getEmail() {
        return email;
    }

    public String getUid() {
        return uid;
    }

    public String getPhotoUrl() {
        return photoUrl;
    }
}

HASIL

 

 

 

 

 

 

 

 

Power Point

PMO rps 8 firebase
Source Code

PMO rps 8 firebase