Practice with Firebase Remote Config in Flutter application

This tutorial is a practice on firebase_remote_config package for Flutter application.

👉 About Firebase Remote Config

A cloud service that lets you change the behavior and appearance of your app without requiring users to download an app update. More information: firebase.google.com/docs/remote-config

👉 Which use case should use Remote Config?

  • A/B testing
  • UI/UX localization
  • Small app data

1. Add dependencies to pubspec.yaml of Flutter project:

firebase_core: ^1.6.0
firebase_remote_config: ^0.10.0+5

2. Configurage Firebase project

Note: In this tutorial, I made it for Android and iOS only.

2.1. Setup for Android:

  • Guideline: firebase.flutter.dev/docs/installation/andr..
  • Quick steps (For those who are lazy to read above 😴)
  • Download config file (google-services.json) from console.firebase.google.com and copy to /android/app/
  • Add this to android/build.gradle:
buildscript {
dependencies {
//...
classpath 'com.google.gms:google-services:4.3.8'
}
}
  • Add this to android/app/build.gradle:
apply plugin: 'com.google.gms.google-services'android {
defaultConfig {
//...
multiDexEnabled true
}
}
dependencies {
implementation 'com.android.support:multidex:1.0.3'
}
  • Add this to android/app/src/debug/AndroidManifest.xml:
<application android:usesCleartextTraffic="true">
</application>

2.2. Setup for iOS:

  • Guideline: firebase.flutter.dev/docs/installation/ios
  • Quick steps (For those who are lazy to read above 😴)
  • Download config file (GoogleService-Info.plist) from console.firebase.google.com
  • Open Xcode `/ios/{projectName}.xcworkspace
  • Right-click on Runner, choose Add files and navigate to select GoogleService-Info.plist file.
  • Add this to ios/Runner/Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
  • Add this to ios/Podfile:
# Uncomment this line to define a global platform for your project
platform :ios, '10.0'
#...
target 'Runner' do
#...
pod 'Firebase/Core'
end

2.3. Add Remote Config data:

{
"name":"erik",
"age":"100",
"address":"711-2880 Nulla St.",
"job":"Poor developer",
"email":"erik@gmail.com"
}

Remember to publish changes !!!

3. Implementation in Flutter project

  • Init Firebase:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
  • Create entity that mapping with value from RemoteConfig:
class UserEntity {
String? name;
String? age;
String? address;
String? job;
String? email;
UserEntity({
this.name,
this.age,
this.address,
this.job,
this.email});
UserEntity.fromJson(dynamic json) {
name = json['name'];
age = json['age'];
address = json['address'];
job = json['job'];
email = json['email'];
}
Map<String, dynamic> toJson() {
var map = <String, dynamic>{};
map['name'] = name;
map['age'] = age;
map['address'] = address;
map['job'] = job;
map['email'] = email;
return map;
}
}
  • Init a RemoteConfig instance:
class _MyHomePageState extends State<MyHomePage> {  final RemoteConfig remoteConfig = RemoteConfig.instance;
@override
void initState() {
super.initState();
() async {
await remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(seconds: 10),
minimumFetchInterval: const Duration(hours: 1),
));
}();
}
  • I want to parse json String in background, so I will use Isolate here. By the way, for anyone needed, this video explains the diff between Async vs Isolates (ez to understand):
class Util {Future<UserEntity> parseJsonConfig(String rawJson) async {
final Map<String, dynamic> parsed = await compute(decodeJsonWithCompute, rawJson);
final userEntity = UserEntity.fromJson(parsed);
return userEntity;
}
static Map<String, dynamic> decodeJsonWithCompute(String rawJson) {
return jsonDecode(rawJson);
}
}
  • Function to fetch remote data:
Future<void> _syncData() async {
showLoading(context);
try {
await remoteConfig.fetchAndActivate();
final rs = remoteConfig.getString(remote_user_data_key);
Navigator.pop(context); // hide loading
} catch (e) {
print(e);
}
}
void showLoading(BuildContext context) {
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 8.0),
Text('Loading...')
],
),
);
},
);
}
  • OK, now I need a notifier to UI when data has changed. By I don’t like setState() very much, I'm going to use ValueNotifier here:
class DataValueNotifier extends ValueNotifier<UserEntity?> {
DataValueNotifier() : super(null);
}
...
class _MyHomePageState extends State<MyHomePage> {
final dataNotifier = DataValueNotifier();
final util = Util();
... Future<void> _syncData() async {
...
dataNotifier.value = await util.parseJsonConfig(rs);
...
}
  • Now, when the data has already, let’s show it:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ValueListenableBuilder(
valueListenable: dataNotifier,
builder: (context, UserEntity? value, child) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Name: ${value?.name}'),
Text('Age: ${value?.age}'),
Text('Address: ${value?.address}'),
Text('Email: ${value?.email}'),
Text('Job: ${value?.job}'),
],
),
);
}
),
floatingActionButton: FloatingActionButton(
onPressed: () => _syncData(),
tooltip: 'Sync',
child: Icon(Icons.sync),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}

👉 Demo

youtube.com/watch?v=p1BfHKKcpNw

Full example source code

flutter-firebase-remoteconfig-sample

Poor developer with a keyboard