https://kotlinlang.org logo
#apollo-kotlin
Title
# apollo-kotlin
s

Stylianos Gakis

01/31/2023, 3:28 PM
I have a thought that I’d like to hear opinions about. I know toFlow() exists, where you can simply call that instead of
execute
on an operation, since that’s what it does internally anyway. So at any query we could theoretically replace it with
toFlow
and assume all responses can return more results instead of just one (I think with deferred responses that’s was the idea) Now I have a screen in Android, where I was checking out removing all configuration changes, so I applied all of these and let compose handle all config changes (testing language changes here in my case). Thing is, for that screen I am testing, I realize we have one part of the screen which shows some string that comes from the backend as-is, meaning it takes what locale header we send it, and responds accordingly, while the other strings of the text are local, so they update immediately when we do a language change, which does not restart the activity, since the Apollo query isn’t re-fetched obviously. If I let the activity re-create itself I get that for free since the entire thing restarts from the beginning. So finally what I am wondering about. Is there any way I can somehow hook something into the apollo-client, so that if there is ever a locale change, I can ask it to re-fetch every flow it’s got open at the moment, where it will then fetch the correct locale since the interceptor will then get the correctly changed locale and append it in the HTTP headers? Does this idea even make sense conceptually? 😄
👀 1
m

mbonnin

01/31/2023, 3:36 PM
I think that can be done outside apollo ? Like writing an extension function that listens to config changes and restarts the Flow when a config change happens?
Something like
Copy code
fun <D: Query.Data> ApolloClient.configChangeAwareQuery(query: Query<D>): Flow<ApolloResponse<D>> {
  // Replace with a global Flow that emits on each config change
  val configChangeEvents: Flow<Unit> = flowOf(Unit)

  return flowOf(Unit).onCompletion { emitAll(configChangeEvents)}.flatMapLatest {
    query(query).toFlow()
  }
}
s

Stylianos Gakis

01/31/2023, 5:42 PM
Hmm yeah interesting. I wonder if there’s a way in Android to hook into this and get notified when there are such configuration changes. Inside a composable context I get notified due to fetching the configuration from a CompositionLocal which changes and triggers recomposition automatically. I will have to play around with this a bit and see if there's a way to be notified in this context but I really can't think of a way right now. It's also that I'm not on a single activity app, so I'm not sure what "global flow that emits on config change" would look like and how it'd be provided to the places where I call Apollo, which often is a couple of layers away from where the activity is.
m

mbonnin

01/31/2023, 5:44 PM
I was going to suggest hooking into
Activity.*onConfigurationChanged*
and updating a StateFlow singleton but if you have several Activities, this effectively breaks
Oh wow, that link about configChanges is 2 years old now 😅
Technically, you could have a composable wrapper that's hosted in your activity with the same idea of a composition local (not sure if taht makes any sense 😅 )
b

bod

01/31/2023, 5:50 PM
at least for the language change, could listening to this broadcast do the trick?
m

mbonnin

01/31/2023, 6:00 PM
That might work. I wonder whether it would maybe glitch if it doesn't come in the same frame as the configChange
s

Stylianos Gakis

02/04/2023, 8:51 PM
I'm planning on trying this out btw, just have been and will be afk for some days now, I'll definitely come back here with what I tried out and how it looked like 😊
Yeah found some time to test, turns out just adding a dumb class like this without even touching manifests and whatnot.
Copy code
class LanguageChangeObserver(private val context: Context) {
  fun listenToChanges(): Flow<Unit> {
    return channelFlow {
      channel.trySend(Unit)
      val broadcastReceiver = Receiver { this.channel.trySend(Unit) }
      broadcastReceiver.register(context)
      invokeOnClose { context.unregisterReceiver(broadcastReceiver) }
    }
  }
  private class Receiver(
    private val localeChanged: () -> Unit,
  ) : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) { localeChanged() }
    fun register(context: Context) { 
      context.registerReceiver(this, IntentFilter(Intent.ACTION_LOCALE_CHANGED)) 
    }
  }
}
And then on the call site, wrapping my flow with a
Copy code
languageChangeObserver.listenToChanges().flatMapLatest {
  apolloClient.flow.blah blah
}
Does seem to work just fine. This in practice of course means that on a language change, my UI elements which are saved locally first instantly change due to the config change, and then as the new GQL response comes in some milliseconds later that text changes too. Interesting that at least I know this is possible in a quite simple way. Of course would be annoying/easy to miss to not have this in all places where I got a flow from GQL, but I mean maybe there’s some room to make a nice API around this in a centralized manner. Thanks for the responses btw, good to know this isn’t all that hard after all.
4 Views