Skip to content

Latest commit

 

History

History
92 lines (54 loc) · 4.83 KB

File metadata and controls

92 lines (54 loc) · 4.83 KB

Stream Android - shared SDK patterns

This file holds the shared Android patterns that cut across Chat, Video, and Feeds. Load it before product-specific references when you need lifecycle, auth, or architecture guidance.

Client model: one-per-process vs one-per-user. Chat and Video run one client per process: ChatClient.Builder(...).build() and StreamVideoBuilder(...).build() register process-wide singletons (ChatClient.instance() / StreamVideo.instance()), and you swap the identity on that single client via connectUser / disconnect (Chat) or logOut + StreamVideo.removeClient + a fresh builder (Video). Feeds is different: FeedsClient is constructed with the User and tokenProvider baked in, there is no instance() accessor, and there is no connectUser call to change identity afterwards. The Feeds client is bound to one user for its lifetime — to operate as a different user you must disconnect() the current FeedsClient and build a new one with the new User + tokenProvider. Hold the reference yourself (lateinit on Application, Hilt @Singleton, Koin single); the SDK won't keep it alive for you.


App shapes

Match the project that already exists:

  • Compose-only app: ownership usually starts in Application and a single-Activity host with a NavHost
  • XML / Views app: ownership usually starts in Application, an AppCompatActivity, or a Fragment-based shell
  • Mixed app: keep Stream setup at the shared boundary (Application or app-scoped DI) instead of duplicating it per screen

Do not rewrite the app into a different UI framework unless the user asks.


Client ownership

Create each Stream client once, store it in an owned object, and pass or inject it from there.

Good ownership points:

  • Application.onCreate()
  • a Hilt @Singleton provider, Koin single, or other app-scoped DI binding
  • a composition root or App initializer that runs before any Stream UI is rendered

Bad ownership points:

  • inside a @Composable body
  • inside a remember { ... } factory
  • inside a leaf Activity or Fragment that does not own app lifecycle

Exact builder shapes and singleton accessors live in the matching reference file.


Auth model

Use the simplest token shape that matches the user's environment:

  • Backend exists: prefer a backend-issued Stream token via a TokenProvider - the SDK will call it again automatically when the token expires.
  • No backend / demo flow: generate a token with the Stream CLI (binary is stream - see credentials.md). Never-expiring: getstream token <user_id>. Expiring: getstream token <user_id> --ttl 1h (units: s/m/h/d).
  • User pastes their own: accept it and pass it to the client.

Keep the split clear:

  • client: API key, User (id, name, image), user token
  • server: API secret and token minting (the CLI handles this automatically)

If the app already has its own auth system, extend that flow instead of adding a second login model beside it. Connect / build once per user session, not on every screen entry.


State and ViewModels

Stateful SDK helpers should have explicit ownership:

  • Compose: obtain ViewModels via viewModels { factory } (Activity / Fragment) or hiltViewModel() (Hilt). Pass them into Composables; never remember { SomeViewModel(...) }.
  • Views: retain ViewModels via ViewModelProvider or DI; collect SDK Flows in repeatOnLifecycle(Lifecycle.State.STARTED).

Avoid creating query objects, channel controllers, message lists, or call objects inside Composables — hoist them into a ViewModel.


Main-dispatcher boundaries

Keep UI updates on the main dispatcher. In Compose, collect Flow state with collectAsStateWithLifecycle(); in Activities / Fragments, use lifecycleScope.launch { repeatOnLifecycle(STARTED) { ... } }.

Preserve the project's concurrency style. If the app already uses coroutines + Flow, stay there. If parts of it still use callbacks, bridge carefully instead of rewriting unrelated code.


Combined Chat + Video apps

The Android Chat and Video modules don't collide at the type level (User, theming, and DI primitives live under different package names). Build both clients in Application.onCreate() with the same API key and JWT token.


Verification checklist

Before calling the work done, confirm:

  • the right Stream artifact is added to the right module's build.gradle.kts
  • each Stream client is built before any Stream UI renders
  • for Video, CAMERA / RECORD_AUDIO are requested at runtime before the first call.join(...)
  • the requested user can connect without leaking the API secret
  • the edited flow works within the existing navigation structure (Compose NavHost, Navigation Component, or Fragment back stack)
  • user switching or logout tears down the previous session cleanly