Skip to content

Android side

crc32 edited this page Mar 8, 2022 · 7 revisions

Architecture

  • Android part of the App is 100% written in Kotlin
  • Dagger dependency injection is used to glue together different classes in the app
  • Since the nature of the app (communicating to the remote watch) is inherently asynchronous, app employs heavy use of Kotlin coroutines and Flows.

Communicating with Flutter

Android app uses a bunch of FlutterBridge classes to implement pigeon communication (see Flutter to Native for more on Pigeon).

Bridge classes are located in the bridges package and are split into three categories:

  • Common bridges

    These bridges can be called from either UI or background flutter isolate. Most bridges should be added into this category. New bridges are added to CommonBridgesModule.

  • UI bridges

    These bridges can only be called from UI flutter isolate. Unlike other two, you can inject MainActivity in its constructors to access UI-related code (such as requesting permissions). New bridges are added to UiBridgesModule.

  • Background bridges

    These bridges are mostly used for Native -> background Flutter triggers. Unlike other two, these are usually Singletons (since background Flutter is also Singleton) and can be injected into other classes which allows other classes to trigger flutter methods. New bridges are added to BackgroundBridgesModule.

To create new bridge:

  1. Create new class in the respective subfolder of the bridges package. Class must implement FlutterBridge package.
  2. Add @Inject constructor
  3. Add bridge to its respective module (see above list for name of the module for each bridge type). See existing module code for example.
  4. If your bridge needs Flutter -> Native callbacks, implement flutter interface, inject BridgeLifecycleController and then set it up with bridgeLifecycleController.setupControl(Pigeons.MyInterface::setup, this). Use this instead of directly calling MyInterface.setup to ensure flutter interface will be automatically unregistered when bridge is disposed.
  5. Write your bridge code ;)

Asynchronous calls

All pigeon callbacks are being synchronously executed on the main thread. Pigeon does not support asynchronous calls yet on native. If you want to make asynchronous pigeon callback, you have to look up the raw name of the Pigeon method in the generated Pigeons.java and then use BinaryMessenger.registerAsyncPigeonCallback to register your callback instead of bridgeLifecycleController.setupControl. Look up existing usages of this method for examples.

Handlers

Whenever watch is connected or connecting, WatchService is active. However, service class is just a shell that handles service lifecycle. Most of the Cobble app logic is contained in the handlers.

Handler is a class that observes some external event (such as new Bluetooth message from the watch or user timezone change) and react to them accordingly. To add new handler, create a class that implements CobbleHandler, add @Inject constructor to that class and register the class in the ServiceModule, similarly to how previous handlers are added.

All handlers are started when the watch is connected (you can assume that watch is either Connected when handler instance is being created). Provided coroutine scope is stopped whenever watch is disconnected (including if it goes into connecting mode). At this point, handlers should stop all listeners and release all resources. If you have to close anything manually, you can do so in coroutineScope.coroutineContext.job.invokeOnCompletion {} callback.

Clone this wiki locally