Implement a Companion App

实现一个配套的应用

This lesson teaches you to

本节课您将学习

  1. Create a companion app module

  2. 创建一个配套应用模块

  3. Display database entries with Firebase UI

  4. 使用 Firebase 可视化页面显示数据

Try it out

试一试

A smart doorbell might or might not have a local display. A companion app for mobile devices allows the user to remotely and automatically see new doorbell ring events and images as they are inserted by the embedded device.

智能的门铃可能会有也可能没有本地显示设备。与之配套的移动设备应用程序允许用户远程自动查看新进的门铃按铃事件和拍摄到的图片。

In this lesson, you will build an Android mobile app containing a Firebase Realtime Database that is synchronized with the doorbell database.

在本节课中,您将编译一个包含可以同步门铃数据库的 Firebase 实时库数据的 Android 移动应用。

Create a companion app module

创建一个配套的应用模块


Create a second module in your project alongside the embedded doorbell app. To create a new project module:

在您工程中同嵌入式门铃应用同级目录中创建另一个模块。创建一个新的工程模块,需要:

  1. Follow the instructions to Create a New Module in Android Studio.

  2. 在 Android Studio 中按照教程 创建一个新的模块

  3. Create a new Phone & Tablet Module.

  4. 创建一个新的 手机 & 平板模块

Add Firebase to your module

在您的模块中添加 Firebase 支持


To enable Firebase Realtime Database for your companion app module:

为了让您的应用模块支持 Firebase 实时数据库,您需要:

  1. Install the Firebase Android SDK into your app module.

  2. 在您的应用模块中安装 Firebase Android SDK

  3. Download and install the google-services.json file as described in the instructions.

  4. 根据说明和介绍下载并安装 google-services.json 文件。

Note: For this code sample, you will use the same google-services.json file for both the app module (writes data) and the companion app module (reads the data). In a production environment, each module should have its own google-services.json, and they should not replace one another.

注意:在该代码示例中,您将使用同应用模块(写入数据)以及配套应用模块(读取数据)相同的 google-services.json 文件。在生产环境中每个模块应该有自己的 google-services.json 文件,并且不能相互替换。

  1. Add the Firebase Realtime Database and Firebase UI dependencies to your app-level build.gradle file:

  2. 在您应用的 build.gradle 文件中添加 Firebase 实时数据库和 Firebase 可视化界面依赖:

  1. dependencies {
  2. ...
  3. compile 'com.google.firebase:firebase-core:9.4.0'
  4. compile 'com.google.firebase:firebase-database:9.4.0'
  5. compile 'com.firebaseui:firebase-ui-database:0.5.3'
  6. }

Display doorbell events with Firebase UI

在 Firebase 可视化界面中显示门铃事件


To simplify the interaction with the Firebase data model, create a DoorbellEntry model class that contains the same properties described in the JSON schema.

为了简化 Firebase 数据模型的交互,请一个 DoorbellEntry 模型类,其包含与之前 JSON 文件结构相同的属性。

  1. public class DoorbellEntry {
  2. Long timestamp;
  3. String image;
  4. Map<String, Float> annotations;
  5. public DoorbellEntry() {
  6. }
  7. public DoorbellEntry(Long timestamp, String image, Map<String, Float> annotations) {
  8. this.timestamp = timestamp;
  9. this.image = image;
  10. this.annotations = annotations;
  11. }
  12. public Long getTimestamp() {
  13. return timestamp;
  14. }
  15. public String getImage() {
  16. return image;
  17. }
  18. public Map<String, Float> getAnnotations() {
  19. return annotations;
  20. }
  21. }

The FirebaseRecyclerAdapter from the FirebaseUI library simplifies binding Firebase data to a Recyclerview for display. Subclasses of the adapter must override populateViewHolder() and provide the logic to bind the data from the model into the provided ViewHolder.

FirebaseUI 中的 FirebaseRecyclerAdapter 简化了在 Recyclerview 中展示 Firebase 数据的操作。适配器的子类必须重写 populateViewHolder() 方法并且需要提供在 ViewHolder 中进行数据绑定的逻辑。

The following DoorbellEntryAdapter binds data from a DoorbellEntry instance into a DoorbellEntryViewHolder:

在下面代码中,DoorbellEntryAdapter 适配器将 DoorbellEntry 实例的数据绑定到 DoorbellEntryViewHolder 上:

  1. public class DoorbellEntryAdapter extends FirebaseRecyclerAdapter<DoorbellEntry, DoorbellEntryAdapter.DoorbellEntryViewHolder> {
  2. /**
  3. * ViewHolder for each doorbell entry
  4. */
  5. static class DoorbellEntryViewHolder extends RecyclerView.ViewHolder {
  6. public final ImageView image;
  7. public final TextView time;
  8. public final TextView metadata;
  9. public DoorbellEntryViewHolder(View itemView) {
  10. super(itemView);
  11. this.image = (ImageView) itemView.findViewById(R.id.imageView1);
  12. this.time = (TextView) itemView.findViewById(R.id.textView1);
  13. this.metadata = (TextView) itemView.findViewById(R.id.textView2);
  14. }
  15. }
  16. private Context mApplicationContext;
  17. public DoorbellEntryAdapter(Context context, DatabaseReference ref) {
  18. super(DoorbellEntry.class, R.layout.doorbell_entry, DoorbellEntryViewHolder.class, ref);
  19. mApplicationContext = context.getApplicationContext();
  20. }
  21. @Override
  22. protected void populateViewHolder(DoorbellEntryViewHolder viewHolder, DoorbellEntry model, int position) {
  23. // Display the timestamp
  24. CharSequence prettyTime = DateUtils.getRelativeDateTimeString(mApplicationContext,
  25. model.getTimestamp(), DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0);
  26. viewHolder.time.setText(prettyTime);
  27. // Display the image
  28. if (model.getImage() != null) {
  29. // Decode image data encoded by the Cloud Vision library
  30. byte[] imageBytes = Base64.decode(model.getImage(), Base64.NO_WRAP | Base64.URL_SAFE);
  31. Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
  32. if (bitmap != null) {
  33. viewHolder.image.setImageBitmap(bitmap);
  34. } else {
  35. Drawable placeholder =
  36. ContextCompat.getDrawable(mApplicationContext, R.drawable.ic_placeholder);
  37. viewHolder.image.setImageDrawable(placeholder);
  38. }
  39. }
  40. // Display the metadata
  41. if (model.getAnnotations() != null) {
  42. ArrayList<String> keywords = new ArrayList<>(model.getAnnotations().keySet());
  43. int limit = Math.min(keywords.size(), 3);
  44. viewHolder.metadata.setText(TextUtils.join("\n", keywords.subList(0, limit)));
  45. } else {
  46. viewHolder.metadata.setText("no annotations yet");
  47. }
  48. }
  49. }

Create an instance of the DoorbellEntryAdapter and attach it to a RecyclerView in your activity. The FirebaseUI adapters automatically register event listeners with the database. To avoid memory leaks, initialize the adapter in onStart() and call its cleanup() method in onStop().

创建一个 DoorbellEntryAdapter 实例并且将它关联给您页面的 RecyclerView。FirebaseUI 适配器自动为数据库注册数据监听器。为了避免内存泄漏,在 onStart() 中初始化适配器并且在 onStop() 中调用适配器的 cleanup() 方法。

To ensure that each new doorbell event is visible to the user, scroll the list to the latest item on each data set change. You can listen for changes events using an AdapterDataObserver attached to your adapter.

为了确保最新的门铃事件对用户是可见的,可以在每次数据变化时滚动列表到最新的列表项。您可以为您的适配器绑定一个 AdapterDataObserver 方法监听每一次数据改变。

  1. public class MainActivity extends AppCompatActivity {
  2. private DatabaseReference mDatabaseRef;
  3. private RecyclerView mRecyclerView;
  4. private DoorbellEntryAdapter mAdapter;
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_main);
  9. mRecyclerView = (RecyclerView) findViewById(R.id.doorbellView);
  10. // Show most recent items at the top
  11. LinearLayoutManager layoutManager =
  12. new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true);
  13. mRecyclerView.setLayoutManager(layoutManager);
  14. // Reference for doorbell events from embedded device
  15. mDatabaseRef = FirebaseDatabase.getInstance().getReference().child("logs");
  16. }
  17. @Override
  18. protected void onStart() {
  19. super.onStart();
  20. mAdapter = new DoorbellEntryAdapter(this, mDatabaseRef);
  21. mRecyclerView.setAdapter(mAdapter);
  22. // Make sure new events are visible
  23. mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
  24. @Override
  25. public void onItemRangeChanged(int positionStart, int itemCount) {
  26. mRecyclerView.smoothScrollToPosition(mAdapter.getItemCount());
  27. }
  28. });
  29. }
  30. @Override
  31. protected void onStop() {
  32. super.onStop();
  33. // Tear down Firebase listeners in adapter
  34. if (mAdapter != null) {
  35. mAdapter.cleanup();
  36. mAdapter = null;
  37. }
  38. }
  39. }

Congratulations! You have built a cloud-enabled doorbell for the connected home using Android Things!

祝贺您!您已经使用 Android Things 创建了一个连接到家的云端门铃!