Google FireBaseを使用してAndroidで簡単なチャットを作成する

数日前、Android向けの最もシンプルなチャットの開発を開始し、Firebaseを使用することにしました。Firebaseは、データをJSON形式で保存する使いやすいリアルタイムデータベースです。 Firebaseは完全なAPIと使用法のドキュメントを提供しているという事実にもかかわらず、MVPなどのアーキテクチャテンプレートに適用しようとすると詳細が欠けていることがわかったため、Androidでの実装の理解方法を説明しようと決めました。 また、Firebase-Interactors用に​​、MVPテンプレートを新しいレイヤーで明示的に拡張しました。



基本的なFirebaseテンプレートの作成



Firebaseでのアカウント作成の詳細や「5分間のクイックスタート」については説明しません。そのまま実装に進みます。



まず、Firebaseテンプレートで必要なディレクトリを見ていきます。たとえば、Firebaseは別のデータベースにユーザーを作成し、保存された情報を作成するときは、メール、パスワード(直接観察することはできません)、一意のUID(ランダムに生成されたキー、ユーザーはライフサイクル全体を順守します)。したがって、ユーザー名を保持したい場合は成功しません。 そのため、テンプレートのディレクトリとして「ユーザー」が必要です。これには、特定の情報を保存できるように、ユーザー名と、場合によってはアバターが含まれます。



現在チャットアプリケーションにログインしているすべてのユーザーを含むcurentUsersというディレクトリもあります。 メッセージを保存するには、メッセージフォルダが必要です。

したがって、3つのディレクトリは、Users、currentUsers、Messagesです。



それらへのリンクは次のとおりです。



「Https:// <your-firebase> / currentUsers /」

「Https:// <your-firebase> / Users /」

「Https:// <your-firebase> / messages /」



これらは、データを追加/取得するときに使用するディレクトリへのリンクであり、基本的にユーザーおよびメッセージシステムが機能するために必要なすべてのものです。



Androidとの実際の会話に移りましょう。 Firebase依存関係をGradleにインポートした場合、Firebaseクライアントのすべての機能を使用できるはずです...チャットアプリケーションには4つの画面があります。





ホーム画面



ここでは、ユーザーが登録(ログイン)するエントリポイントを探し、TextViewに現在のユーザーの数を表示します。



MainActivityPresenter





public class MainActivityPresenterImpl implements MainPresenter { private final MainView mainView; private final MainInteractor interactor; public MainActivityPresenterImpl(MainView view) { this.mainView = view; interactor = new MainInteractor(this); } @Override public void receiveRequest() { interactor.receiveRequest(); } @Override public String getNumberOfUsers(long numberOfUsers) { return "Online users: " + String.valueOf(numberOfUsers); } @Override public void sendNumberOfChildren(long number) { mainView.setNumberOfUsersTextView(getNumberOfUsers(number)); } }
      
      





MainInteractor:



  public class MainInteractor implements MInteractor { private final Firebase mainRef = new Firebase("https://<your-firebase>/currentUsers"); private final MainPresenter presenter; public MainInteractor(MainPresenter pre) { this.presenter = pre; } @Override public void receiveRequest() { mainRef.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { presenter.sendNumberOfChildren(dataSnapshot.getChildrenCount()); }); } }
      
      





ここで何が起こっていますか? インタラクションでは、コンストラクターパラメーターがリンク(currentUsersディレクトリー)であるFirebaseリンクがあり、Firebase currentUsersディレクトリーに1つのリクエストを送信し、DataSnapshot-特別なFirebase関数を受け取るリスナーにリンクを追加します...スナップショットは本質的にすべてのオブジェクトのリストです指定されたディレクトリ内のデータ。したがって、dataSnapshot.getChildrenCount()を実行すると、現在ディレクトリ内にあるオブジェクトの数が取得されます。これは、オンラインのユーザー数と同じです。 TextViewに表示し、ユーザーは自分のピアがネットワーク上にいくつあるかを確認します。 Firebaseとの通信のあらゆる側面でデータをクエリするというこの原則を使用しているため、非常にシンプルですが強力です。



登録画面



前のセクションでメイン画面のコードを見ましたが、これは次のようになります。 さらに、[登録]をクリックして、3段階のプロセスを実行します。最初に[ユーザー名]を選択します。表示される場合はエラーを表示します。そうでない場合は、絵文字フラグメントに移動します。電子メールが送信されない場合に登録を完了する画面。この場合もエラーが発生するため、画面は次のとおりです。



画像



ユーザー名用、電子メール用、パスワード用の簡単なEditTextがあります。 選択する絵文字のグリッド(現在は1行で追加されます)と、認証中に回転アニメーションを表示する進行状況バー。 「登録」ボタンは、「フラグメント」で結合された値を受け入れ、プレゼンターに送信します。



 public class FirebaseUserRegisterPresenterImpl implements FirebaseUserRegisterPresenter { private final RegisterView registerView; private final RegisterInteractor interactor; public FirebaseUserRegisterPresenterImpl(RegisterView view) { this.registerView = view; this.interactor = new RegisterInteractor(this); } @Override public void receiveRegisterRequest(String username, String email, String password, String emoji) { interactor.receiveRegisterRequest(username, email, password, emoji); registerView.spinProgressBar(); } @Override public void onFailure() { registerView.onFailure(); registerView.stopProgressBar(); } @Override public void onSuccess() { registerView.onSuccess(); registerView.stopProgressBar(); } }
      
      





インタラクター:



  public class RegisterInteractor implements RInteractor { private Firebase userRef = new Firebase("https://<your-firebase>/Users/"); private final FirebaseUserRegisterPresenter presenter; public RegisterInteractor(FirebaseUserRegisterPresenter pre) { this.presenter = pre; } @Override public void receiveRegisterRequest(final String username, String email, String password, final String emoji) { userRef.createUser(email, password, new Firebase.ValueResultHandler<Map<String, Object>>() { @Override public void onSuccess(Map<String, Object> stringObjectMap) { String uid = stringObjectMap.get("uid").toString(); userRef = new Firebase("https://<your-firebase>/Users/" + uid); userRef.setValue(createUser(username, emoji)); presenter.onSuccess(); } @Override public void onError(FirebaseError firebaseError) { presenter.onFailure(); } }); } @Override public Map<String, Object> createUser(String username, String emoji) { Map<String, Object> user = new HashMap<>(); user.put("username", username); user.put("emoji", emoji); return user; } }
      
      





ここにいくつかの新しい機能があります:

-.createUser()、. push()、および.setValue()メソッド

-ユーザーUID



.createUser()-ユーザーを作成します! 別のデータベースにあるため、ユーザーを作成するときに、/ Usersディレクトリにオブジェクトを作成する必要があります(表示するには)。

これは、「プッシュ」を押すことにより行われます。 指定された.push()はディレクトリの奥深くに「プッシュ」され、その名前にランダムに生成されたキーを持つサブディレクトリが作成されますが、その前にリンクにUIDをアタッチして、ディレクトリをユーザーUIDと比較できるようにします。 UIDはランダムに生成されたキーであり、サブディレクトリの名前(およびUserオブジェクトのパラメーター)として使用して、特定のUIDに一致するユーザー名を後で特定し、ログイン後にユーザー名を取得するか、currentUsersのChildを削除することもできます(ユーザーを表示します)システムから)。



.setValue()メソッドはオブジェクトをディレクトリに追加するため、必要なデータを保存できます。



ログイン画面



ログイン画面のインターフェースは非常にシンプルです。2つのEditText(メールアドレスとパスワード)とログインボタン、そして物事を盛り上げるためのプログレスバーです。



ユーザーが「ログイン」をクリックするとどうなりますか?



この部分は複雑で、ユーザーが別のデータベースにあることがわかっているので、ユーザーを登録するときに、使用しているユーザー名をどのように見つけるのでしょうか?



これは、前述の/ Usersディレクトリの全体的な目的です。 また、ユーザーUIDに基づいて名前を付けると、対応するUIDを持つディレクトリを簡単に検索できます(たとえば、特定のユーザーから特定の情報を推定する場合)。 また、UIDオブジェクトを呼び出すと、指定されたUIDでオブジェクトを入力し、チャットアクティビティのonTestroy()で削除できます-ユーザーを登録する非常に簡単な方法です。



ログインプレゼンター:



 public class FirebaseLoginPresenterImpl implements FirebaseLoginPresenter { private final LoginView loginView; private final LoginInteractor interactor; public FirebaseLoginPresenterImpl(LoginView view) { this.loginView = view; interactor = new LoginInteractor(this); } @Override public void receiveUserLogin(String email, String password) { loginView.spinProgressBar(); interactor.attemptToLogIn(email, password); } @Override public void onFailure() { loginView.stopProgressBar(); loginView.onFailure(); } @Override public void onSuccess(String user, String uid) { loginView.stopProgressBar(); loginView.logTheUserIn(user, uid); } }
      
      





電子メールとパスワードを受信し、リクエストが完了するまでスクロールバーを表示し、結果に応じて「表示」メソッドを呼び出します。





ユーザー認証が成功した場合、指定されたユーザーのユーザー名を取得してチャット画面に送信しますが、その前にユーザーを/ currentUsersディレクトリに追加して、ログインしているユーザーを確認できるようにします。 デフォルトのAuthDataによって取得され、特定のFirebaseユーザーデータ(UID、認証によって生成された特別なキーなど)の表示に使用されます。



チャット画面



ChatActivityは2つのフラグメントを使用します。1つはメッセージングサービス用で、もう1つはアクティブなユーザーのリストを表示します。 メニューメニューアイコンを1回クリックして、メッセージフラグメントをリストフラグメントに置き換え、もう一度クリックして、BackStackを設定します(そして戻ってきます!)。



画像



ここでの問題は、すべてのデータをFirebaseから取得することです。つまり、Firebaseをビューに実装することはできませんが、ListView / RecyclerViewアダプターはAndroid Viewのコンポーネントでもあります。



答えは再びMVP(+ Interactors)です! 優れたアーキテクチャは、それが実装されるコンポーネントに反映されます。つまり、MVPでアダプターを記述することもできます。これは、「View」コンポーネントです。このコンポーネントには、ListView要素に新しい値を送信する(Interactorから指定された値を要求する)プレゼンターがいますFirebaseへのリンクを持つInteractorによって値が生成されるため、JavaとAndroidをバックエンドから分離できます。



アダプター



 public class CustomMessageRecyclerAdapter extends RecyclerView.Adapter<CustomMessageRecyclerAdapter.ViewHolder> implements MessageAdapterView { private final ArrayList<Message> mMessageList = new ArrayList<>(); private final String user; private final MessagePresenterImpl presenter; public CustomMessageRecyclerAdapter(String username) { this.user = username; presenter = new MessagePresenterImpl(this); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_message, parent, false); return new ViewHolder(v); } @Override public void onBindViewHolder(ViewHolder holder, int position) { Message current = mMessageList.get(position); if (current.getAuthor().equals(user)) { holder.mAuthorTextView.setText("You"); } else { holder.mAuthorTextView.setText(current.getAuthor()); } holder.mMessageTextView.setText(current.getMessage()); holder.mEmojiTextView.setText(current.getEmoji()); } @Override public int getItemCount() { return mMessageList.size(); } @Override public void addItem(Message message) { mMessageList.add(message); notifyDataSetChanged(); } @Override public void request() { presenter.requestMessages(); } public class ViewHolder extends RecyclerView.ViewHolder { private TextView mAuthorTextView; private TextView mMessageTextView; private TextView mEmojiTextView; public ViewHolder(View itemView) { super(itemView); mAuthorTextView = (TextView) itemView.findViewById(R.id.message_author); mMessageTextView = (TextView) itemView.findViewById(R.id.message_value); mEmojiTextView = (TextView) itemView.findViewById(R.id.message_emoji); } } }
      
      





これは非常に簡単です。ViewHolderを拡張するメソッドがあり、前述のホルダーを埋めます。Firebaseからメッセージを要求するメソッドと、表示する新しいメッセージがある場合はArrayListにメッセージを追加するメソッドがあります。



発表者:



 public class MessagePresenterImpl implements MessagePresenter { private final MessageAdapterView adapterView; private final MessageInteractor interactor; public MessagePresenterImpl(MessageAdapterView view) { this.adapterView = view; this.interactor = new MessageInteractor(this); } @Override public void sendMessageToAdapter(Message message) { adapterView.addItem(message); } @Override public void requestMessages() { interactor.request(); } }
      
      





インタラクター



 public class MessageInteractor { private final MessagePresenter presenter; private final Firebase mMessagesRef = new Firebase("https://<your-firebase>/messages"); private final Query mMessageQuery; public MessageInteractor(MessagePresenter pre) { this.presenter = pre; this.mMessageQuery = mMessagesRef.orderByValue().limitToLast(100); } public void request() { mMessageQuery.addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String s) { presenter.sendMessageToAdapter(dataSnapshot.getValue(Message.class)); } //some more auto-generated methods
      
      





アダプターは新しいメッセージを必要とし、Presenterにメッセージを要求するよう指示しますが、これはPresenterの仕事ではないため、InteractorにFirebaseから要求するよう指示します。すべてを変更する必要があります。データのPOJOを設定するだけで、発言者とインタラクターは何をする必要もありません。リクエストは変更されません。 したがって、データ量を切り替える場合、フィールドをPOJOに追加するだけで、別のフィールドを表示する場合は、ビューを変更するだけです(ウィジェットを追加することで)。



リクエストは単にリクエストを意味し、.orderByValue()はそこにオブジェクト(値)を取得することを意味し、.limitToLast(100)は常に最後の100メッセージを取得することを意味します。 チャットがしばらくアクティブである場合、メッセージの断片が破棄/再開されるまで、すべてのメッセージが表示されます(100以降でも)。



また、onDestroy ChatActivityで、UIDをインタラクターに(Present'ator経由で)送信し、currentUsersからユーザーを削除します(終了します)。



 public class ChatLoginInteractor implements CLoginInteractor { @Override public void logTheUserOut(String uid) { Firebase userRef = new Firebase("https://<your-firebase>/currentUsers/" + uid); userRef.removeValue(); //removes the Child from Firebase } }
      
      





仕組み、ステップバイステップ



Android用のFirebaseライブラリは非常によく構築されており、ドキュメントは理解するのが少し複雑ですが、物事を掘って組み合わせようとすると、基本原則を簡単に理解できます。



-Firebaseへのリンクは、変更、リクエスト、または単に新しいデータを追加するディレクトリへの単なるリンクです。



リスナーは「Rxのような」機能を提供し、新しいユーザーの追加を常に監視し(ディレクトリ内の各オブジェクトは子です)、データを操作できます。

DataSnapshotは、1つのディレクトリ内の現在の値のリストです。



AuthDataは、特定のユーザー/リクエスト、UID、一意のキーに関するすべてのデータのバンドルのようなものです...





Firebaseへの参照を使用する前に、各onCreate()メソッドでFirebase.setAndroidContext(this)を呼び出す必要があります



結論



Firebaseはシンプルなバックエンドデータベース用の非常に強力なツールであり、小規模プロジェクトで非常に高速で使いやすいですが、このチャットアプリケーションのようなより複雑なアプリケーションでも使用できます。



クロスプラットフォームなので、Android、iOS、JS用のFirebaseアプリケーションを完全にサポートして作成でき(JSはAngular、React、Nodeをサポートしていると思います)、3つの主要なプラットフォームすべてで同じFirebaseテンプレートを使用できます。



PS FireBaseは常に更新および変更されるため、完全なパフォーマンスを長期間保証することはできません。 いくつかの変更を行うと、状況が変わります。



All Articles