If you're a fan of our Firebase YouTube channel, you might have noticed that we just published a video all about how to best load up Remote Config values in your app:
But we know there are some people out there who would rather read than watch a video, so we thought we'd provide this for you in convenient blog form, too!
So if you've done any Remote Config work in the past, you probably noticed there are three main steps to implementing Remote Config in your app:
- You supply remote config with a bunch of default values. This is done locally on the device, either in code or by pulling in values from a local plist or xml file.
- You then call
fetch()to download any new values from the cloud
- Finally, you call
activateFetched()to apply these downloaded values on top of the default values you originally supplied.
At this point, when you query remote config for a particular value, you'll either get the updated value from the cloud, or whatever default you've supplied locally. And this is nice, because you can wire up your app to grab all of its values through Remote Config. You're still keeping your network calls nice and small, because you're only downloading the values that are different than your defaults, but you now have the flexibility to change any aspect of your app later through the cloud.
Seems pretty straightforward, right? And, for the most part, it is. The problem is that step 2 up there requires making a network call, and out in the real world, where people are using your app in tunnels, elevators, deserts, or places with poor network connections, you never really know how long it's going to take for that network call to complete. You can't guarantee that this process will be finished before your user starts interacting with your app.
So with that said, here are three strategies you can employ:
1. Activate and refresh
This strategy is the most straightforward, which is generally why you'll see it in our sample apps and tutorials. The idea is that, once you've downloaded these new values from the cloud, you can call
activateFetched() in your completion handler to immediately apply them, and then you tell your app to just go ahead update itself.
This strategy is nice in that it allows your user to get right into your app. But it's a little weird in that you potentially have your app changing things like button text, UI placement, or other critical values while your user is in the middle of using it, which can be a poor user experience. This is why many developers will opt to choose a solution like...
2. Add a loading screen
A fairly common strategy is to display a loading screen when your user first starts up your app. Just like before, you'd call
activateFetched in your completion handler to immediately apply these values, but instead of telling your app to refresh its interface, you would tell your app to dismiss the loading screen and transition to the main content. This is nice in that by the time your users make it past your loading screen and into your app, you're almost guaranteed to have fresh values from the cloud downloaded and ready to go.
Obviously, the big drawback here is that now you have a loading screen. This requires time and effort to build, and is also a barrier to your users getting into your app. But if your app already has a loading screen because you have to perform other work and/or network calls at startup, then this could be a good strategy. Just fetch your remote config values alongside whatever other startup work you're doing.
However, if your app doesn't already have a loading screen, I'd recommend trying an alternate strategy instead. Something like...
3. Load values for next time
This one seems a little counter-intuitive, but hear me out: When your user starts up your app, you immediately call
activateFetched(). This will apply any old values you've previously fetched from the cloud. And then your user can immediately start interacting with your app.
In the meantime, you kick off an asynchronous
fetch() call to fetch new values from the cloud. And in the completion handler for this call… you do nothing. Heck, you don't even need add a completion handler in the first place. Those values you fetched from the cloud will remain stored locally on the device until your user calls
activateFetched the next time they start your app.
This strategy is really nice in that your users can immediately start using their app, and there's no weird behavior where your app's interface suddenly changes out from under them. The obvious drawback is that it takes two sessions for your users to see your updated remote config values. You'll need to decide for yourself whether or not this is the right behavior for your app.
If you're using Remote Config to, say, fine-tune some values in your tower defense game, this is probably fine. If you're using it to deliver a message-of-the-day, or content specific to certain dates (like a holiday re-skin of your app) it might not be.
3.5. Some hybrid of strategies 2 and 3. Or 1 and 3.
One nice thing about Remote Config is that it can tell you when your last successful
fetch() call was performed. And so you can build some hybrid strategy where you first check how old your most recently fetched Remote Config data was. If it's fairly recent (say, in the last 48 hours or so), you can go ahead and run the "apply the latest batch of values and perform another fetch for next time" strategy. Otherwise, you can fall back to one of the earlier two strategies.
Let's talk about caching
As long as we're talking about loading strategies, let me hit on a related topic; caching. It sometimes confuses developers that values from remote config are cached for 12 hours instead of being grabbed immediately every time you call
fetch(). And while you can reduce this cache time somewhat, if you start making network calls too frequently, your app might start getting throttled, either by the client, or the Remote Config service.
So, why do we have all of this caching and throttling behavior in the first place?
Well, partly it's a way to ensure we can keep this service free, even if your app scales to millions upon millions of users. By adding in some well-behaved caching, we can make sure that our service can handle your app for free, no matter how popular it gets.
It's also a nice way to protect the service from bad code. There are some developers out there (not you, of course) who might accidentally call
fetch() too frequently. And we don't want the entire service getting slammed because one developer out there is accidentally DDoSing it. Having the client-side library serve up cached values and/or throttle frequent network calls helps keep the service safe.
But it's also good for your users. A good caching strategy prevents your app from using up your user's battery and/or data plan by making too many unnecessary network calls, which is always a good thing.
Obviously, while you're developing and testing your Remote Config implementation, this can be inconvenient, which is why you can override the local throttling behavior by turning on developer mode. But out in the real world, even if your users are using your app every single day, this kind of caching behavior is usually just fine.
But what if you want to push out values more frequently than that? What if you're trying to deliver a "Message of the hour" kind of feature through Remote Config? Well, frankly, this might be a sign that the Remote Config service isn't the ideal solution for you, and you should consider using the Realtime Database instead for something a little more timely.
On-demand updates with FCM
On the other hand, there may be times when you want to infrequently apply an urgent Remote Config update. Perhaps you've accidentally broken your in-app economy with your latest batch of changes and you want everybody to get new values right away. How can you get around the cache and force your clients to perform a fetch?
One solution is to use Firebase Cloud Messaging. With FCM, you can send a data-only notification to all of your devices, letting them know that there's an urgent update pending. Your apps can respond to this incoming notification by storing some kind of flag locally. The next time your user starts up your app, it can look for this flag. If it's set to true, your app can perform a fetch with a cache time of 0 to ensure an immediate fetch. Just make sure you set this flag back when you're done, so your app can resume its usual caching behavior.
You might be tempted to have your clients respond to this incoming notification by immediately waking up and fetching new values from the Remote Config service, but if every instance of your app did that at once, there's a very good chance it will hit the server-side throttle, so I don't recommend this. Stick with the above strategy instead; that will spread out the network calls across the time period it takes for all of your users to open up your app.
So there ya go, folks! A few tips and tricks for loading up your values from Remote Config. Got any tips of your own? Feel free to share them in the comments on our YouTube video, or head on over to the Firebase Talk group and let us know how you're coming along with your Remote Config implementation.
Yes, that was kind of a pun. Cache hits; get it?