flutter WebView lose pseudo of my chat page after each open app - wordpress

I have a webview on a Pageview who display a chat from a plugin that I use on my wordpress website (I have no access to data from this plugin). It's not a chat with FB or google account, it's only an open chat room, where users can add and save her nickname (I suppose nickname is stored in cookies ?). As long as the webview is active the nickname remains memorized. Problem, after each time the app is close and reopen, the user lose his nickname.
Here is my code
WebView(
initialUrl: 'https://XXXX',
javascriptMode: JavascriptMode.unrestricted,
gestureRecognizers: [
Factory(() => PlatformViewVerticalGestureRecognizer()),
].toSet(),
),
How can I save session ? Even when after app is close and reopen ?

First, in your website project, add this javascript code which it will be accessible to the HTML pseodo input:
var psuedoInput = document.querySelector('inputSelectorHere');
_selector.addEventListener('change', function(event) {
var message = psuedoInput.value;
if (messageHandler) {
messageHandler.postMessage(message);
}
});
you can add it inside a <script></script> in the .html file or in a .js separate file.
this basically will post a message with the pseudo input value to our app later.
Don't forget to change inputSelectorHere with your psuedo input selector.
now in your flutter code, create a simple Stirng variable like this:
String? cookie;
then in the WebView widget:
WebView(
javascriptChannels: <JavascriptChannel>[
// javascript channel that saves the cookie
JavascriptChannel(
name: 'Cookie',
onMessageReceived: (JavascriptMessage message) {
cookie = message.message;
print("cookie: $cookie");
},
),
].toSet(),
onWebViewCreated: (controller) {
if (cookie == null) {
return;
}
controller.runJavascript("document.cookie = '$cookie';");
// }
},
initialCookies: [],
initialUrl: 'https://XXXX',
javascriptMode: JavascriptMode.unrestricted,
),
here the JavascriptChannel is set so it receives those messages which will be sent from your website from the webview, then it will be saved inside the cookie variable which we created.
when you close the webview and open it again, the onWebViewCreated will be called, and the cookie now is not null, so it will assign the cookie we saved to document.cookie in the webview.

As I can understand. You just need to get cookies (or cache and local storage) and store them in FlutterSecureStorage. when the user closes the app and re-opens just check if cookies are stored in FlutterSecureStorage or not.
If Cookies are present just add the cookies and refresh the page. I have written a pseudo code for the demo purpose (Code might not work as you expected but it will give you a brief idea about my approach).
I have added a code for the cookies. I also added code for the cache and local storage but you have to configure it according to your needs.
Please read the comments.
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:webview_flutter/webview_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(const FlutterWebViewDemo());
}
class FlutterWebViewDemo extends StatefulWidget {
const FlutterWebViewDemo({Key? key}) : super(key: key);
#override
State<FlutterWebViewDemo> createState() => _FlutterWebViewDemoState();
}
class _FlutterWebViewDemoState extends State<FlutterWebViewDemo> {
late final WebViewCookieManager cookieManager = WebViewCookieManager();
var controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setBackgroundColor(const Color(0x00000000))
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
// Update loading bar.
},
onPageStarted: (String url) {},
onPageFinished: (String url) {},
onWebResourceError: (WebResourceError error) {},
),
)
..loadRequest(Uri.parse(''));
/// <---- please add the url here
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
children: [
Expanded(child: WebViewWidget(controller: controller)),
ElevatedButton(
onPressed: () async {
_onListCache();
},
child: const Text("Save Cache"),
),
ElevatedButton(
onPressed: () async {
_onAddToCache();
},
child: const Text("Set Cache"),
),
ElevatedButton(
onPressed: () async {
_onClearCache();
},
child: const Text("Clear Cache"),
),
ElevatedButton(
onPressed: () async {
_onListCookies();
},
child: const Text("Save Cookies"),
),
ElevatedButton(
onPressed: () async {
_onSetCookie();
},
child: const Text("Set Cookies"),
),
ElevatedButton(
onPressed: () async {
_onClearCookies();
},
child: const Text("Clear Cookies"),
)
],
),
),
);
}
Future<void> _onListCookies() async {
final String cookies = await controller
.runJavaScriptReturningResult('document.cookie') as String;
FlutterSecureStorage secureStorage = const FlutterSecureStorage();
secureStorage.write(key: 'cookies', value: cookies);
}
Future<void> _onSetCookie() async {
FlutterSecureStorage secureStorage = const FlutterSecureStorage();
String? cookies = await secureStorage.read(key: 'cookies');
/// get cookies from flutter secure storage and set them and refresh the page with new cookies.
/// please fill the required fields.
await cookieManager.setCookie(
WebViewCookie(
name: '',
/// required you have to set this
value: cookies!,
domain: '',
/// required
path: '/',
/// required
),
);
/// this will load the new page
await controller.loadRequest(Uri.parse(
'',
/// <---- refresh url
));
}
Future<void> _onClearCookies() async {
final bool hadCookies = await cookieManager.clearCookies();
String message = 'There were cookies. Now, they are gone!';
if (!hadCookies) {
message = 'There are no cookies.';
}
print(">>>>>>>>>> message $message");
}
Future<void> _onAddToCache() async {
/// <--- you have to write the logic to add cache and local storage from flutter secure storage. like this and refresh the page.
await controller.runJavaScript(
'caches.open("test_caches_entry"); localStorage["test_localStorage"] = "dummy_entry";',
);
}
Future _onListCache() async {
await controller.runJavaScriptReturningResult('caches.keys()');
/// <--- get cache and local storage and save it in flutter secure storage.
}
Future<void> _onClearCache() async {
await controller.clearCache();
await controller.clearLocalStorage();
}
}

Related

Refresh/Reset StreamProvider - Flutter Firebase

In my app, I have the following providers.
#override
Widget build(BuildContext context) {
return OverlaySupport.global(
child: MultiProvider(
providers: [userLoggedIn, currentUserData],
child: MaterialApp(...)))
}
var userLoggedIn = StreamProvider<User?>.value(
value: FirebaseAuth.instance.authStateChanges(), initialData: null);
var currentUserData = StreamProvider<FrediUser>.value(
updateShouldNotify: (_, __) => true,
initialData: FrediUser(
loginProvider: '',
email: '',
admin: false,
profileSettings: [],
profileChips: [],
profileStats: [],
id: 'loading',
imageUrl: 'loading',
bio: 'loading',
username: 'loading'),
value: currentUserID != null ? currentUserDataStream() : null,
);
PROBLEM
When the user logs out (or logs in for the first time), the provider is either:
Containing old user data (until a hot restart is done, when the providers are called again and reloaded)
Null or empty, because there was no user before.
What I want to do is to refresh or call the Stream Providers again once I have a new user, or delete all the data once a user logs off.
Thank you!
You can listen to the changes of auth state like this.
FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
I've been facing a similar problem as you are, I've come up with a work-around although not sure how "valid" it is according to the Provider architecture
The Problem
I've got a DatabaseService class which has a stream function of type Stream<CustomUser> function, and I used it like this:
//--- main.dart ---//
runApp(MultiProvider(
providers: [
// ..some other providers.. //
// data provider
Provider<DatabaseService?>(
create: (_) => databaseService,
),
// data provider
StreamProvider<CustomUser?>(
create: (context) => databaseService.getCurrUserFromDb(),
lazy: false,
initialData: null,
updateShouldNotify: (_, __) => true,
),
],
child: MyApp(
initPage: initPage,
)
));
Stream Function:
//--- database_service.dart ---//
// gets the user from database and
// assigns it to the variable _user.
Stream<CustomUser?> getCurrUserFromDB() async* {
try {
CustomUser? currUser;
if (_user != null) {
await for (DocumentSnapshot<Object?> event
in users.doc(user.uid).snapshots()) {
final jsonMap = event.data() as Map<String, dynamic>;
currUser = CustomUser.fromJson(jsonMap);
_user = currUser;
CustomPreferences.setCurrUser(_user);
yield currUser;
}
}
} catch (e) {
rethrow;
}
}
databaseService is the DatabaseService class with named constructors.
This was not causing the widgets to rebuild at the start nor when the stream has a new value
Solution:
Created a StreamController in the DatabaseService class, and when the user signs in I add the stream function:getCurrUserFromDB() to the StreamController like this
//--- authentication_screen.dart ---//
...
ElevatedButton(
child: const Text("Sign In"),
onPressed: () async {
final user = await AuthService().googleSignIn();
if (user != null) {
final dbProvider = context.read<DatabaseService?>();
await dbProvider?.setInitUser(user, context);
await dbProvider?.cusUserController
.addStream(dbProvider.getCurrUserFromDB());
}
}),
...
setInitUser(CustomUser? user) is used set the value of the _user variable in DatabaseService and user is used to get this variable.
Reasoning
I am creating a StreamProvider at the start of the app, and its source the StreamController needs to have a stream to listen so I give it when I am trying to sign in.
Or even cleaner solution would be to do it in the constructor of DatabaseService Class like this:
//--- database_service.dart ---//
// method to add the stream to controller //
Future<void> addStream() async {
if (!_cusUserController.isClosed && !_cusUserController.isPaused) {
await _cusUserController.addStream(getCurrUserFromDB());
}
}
// constructor //
DatabaseService._init(CustomUser cusUser) {
addStream();
_user = cusUser;
}
And one last thing to note is that I don't make the declare the Controller as final. When I had it declared as final the streams weren't updating, so it looks like this now:
//--- database_service.dart ---//
StreamController<CustomUser?> _cusUserController = StreamController();
TL;DR
I created a StreamProvider which returns a StreamController in its create property and later down the life cycle of the app I gave the controller a Stream using the addStream method.
Sorry for the wall of text I just wanted to come out as clear as possible.

Flutter Web Firestore Running Out of Memory - Provider

I have a very tricky situation, which I've reproduced in a demo.
I have a Provider of a user, with this method of updating the listeners:
class User extends ChangeNotifier {
...
User({required this.uid}) {
Database().getUser(uid).listen(
(user) async {
displayName = user?.displayName;
email = user?.email;
phoneNumber = user?.phoneNumber;
photoURL = user?.photoURL;
did = user?.did;
interests = user?.interests;
notifyListeners();
},
onError: (e) => print(e),
);
}
...
}
My main.dart starts like this:
return MultiProvider(
providers: [
ChangeNotifierProvider<AuthState>.value(value: _authState),
ChangeNotifierProvider<ThemeModel>(create: (_) => ThemeModel())
],
child: Consumer<AuthState>(
builder: (context, auth, child) {
var user =
auth.authUser == null ? null : User(uid: auth.authUser!.uid);
return MultiProvider(
providers: [
ChangeNotifierProvider<ZUser?>.value(
value: zuser,
),
],
child: MaterialApp.router(...
This has been sufficient for my use case thus far.
Now, I wish to make an update to the interests field;
I have a DB widget that does:
Future updateUser(String uid, Map<String, Object?> data) async {
return userCollection.doc(uid).update(data);
}
Where the userCollection is my collection in Firestore.
I call this class from my view widget, as:
ZWideButton(
text: "Save",
onPressed: () async {
setState(() {
_localEdit = false;
_loading = true;
});
await user.saveInterests(_interests());
setState(() => _loading = false);
},
),
Where saveInterests is:
Future saveInterests(List<String> interests) async {
return _db.updateUser(uid, {"interests": interests});
}
None of this presents any problem at first -- I can update the interests and it works fine. That is, until I keep updating the interests, and it gets slower and slower each time (the browser says the download time gets longer and longer) and seemingly my computer is eating up more and more memory until the webpage ultimately crashes.
Something of a memory leak appears to be happening, but I'm unsure what about flutter web and firebase could be causing it. I believe it may have to do with the Provider package not disposing appropriately. It does not seem to be the provider as I don't see the Widget being rebuilt over and over. Looking for some thoughts.
For anyone looking; My issue is that my json deserializer was causing an infinite loop with the firebase listener

Handling different scenarios for app redirection using Firebase Dynamic Links

I am trying to create a streaming app.
I am integrating Firebase Dynamic links, so hosts can share a link to join their session.
But I am kinda lost on how to implement it, and I would like to know your insights about this.
So let me start with the structure, this is the main pages:
MainApp
Authenticate
LoginPage
HomePage
Profile
JoinPage
This is how I setup my app, they are all wrap to Authenticate widget, this widget determines if user should be redirected to login or the home page. Pretty simple eh. :)
Then here comes the Dynamic links.
My dynamic links has a query params on it to determine which session to join. It looks like this:
https://my.app.link/join-session?sessionId=\<some random keys>
And this is what I want to handle it.
If (user is not logged in ) {
// save the sessionId to a singleton (or somewhere that persists along the app lifecycle, I use Provider here)
// redirects user to login page
// user logged in
// upon going to home page, will retrieve the saved sessionId and redirect user to the session using the sessionId
} else {
// retrieve sessionId
// redirect user to the session using the sessionId
}
This is how my code looks:
MainApp.dart logic
....
if (state == PageLoadState.DONE) {
return MultiProvider(
providers: [
Provider<SampleAppAuthProvider>(
create: (_) => SampleAppAuthProvider(FirebaseAuth.instance),
),
Provider<SampleAppConfigProvider>(
create: (_) => SampleAppConfigProvider(sharedPrefs)
),
StreamProvider(
initialData: null,
create: (context) => context.read<SampleAppAuthProvider>().authState,
),
Provider<DynamicLinkService>(create: (_) => DynamicLinkService(instance: FirebaseDynamicLinks.instance)),
ChangeNotifierProvider(create: (_) => UsersApi(repo: Repository('users')), lazy: true),
ChangeNotifierProvider(create: (_) => SessionsApi(repo: Repository('sessions')), lazy: true),
ChangeNotifierProvider(create: (_) => MessagesApi(repo: Repository('messages')), lazy: true),
],
child: MaterialApp(
title: 'SampleApp!',
theme: defaultTheme(),
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
onGenerateRoute: router.generateRoute,
home: Authenticate(),
),
);
}
......
And this is my Authenticate Page:
class Authenticate extends StatelessWidget {
#override
Widget build(BuildContext context) {
context.read<DynamicLinkService>().handleDynamicLinks();
final firebaseUser = context.read<User>();
if (firebaseUser != null) {
return HomePage();
}
return LoginPage();
}
}
I have also create a separate service for handling the dynamic links:
DynamicLinkService.dart
class DynamicLinkService {
DynamicLinkService({#required this.instance});
final FirebaseDynamicLinks instance;
String _sessionIdToJoin = '';
get sessionIdToJoin => _sessionIdToJoin;
void clearSessionId () => _sessionIdToJoin = '';
Future handleDynamicLinks() async {
final PendingDynamicLinkData data =
await instance.getInitialLink();
_handleDeepLink(data);
instance.onLink(
onSuccess: (PendingDynamicLinkData dynamicLink) async {
_handleDeepLink(dynamicLink);
}, onError: (OnLinkErrorException e) async {
print('Link Failed: ${e.message}');
});
}
void _handleDeepLink(PendingDynamicLinkData data) {
final Uri deepLink = data?.link;
if (deepLink != null) {
print('_handleDeepLink | deeplink: $deepLink');
bool isJoiningSession = deepLink.pathSegments.contains('session');
if(isJoiningSession) {
String sessionId = deepLink.queryParameters['sessionId'];
if (sessionId != null) {
_sessionIdToJoin = sessionId;
}
}
}
}
}
I also have another UtilService that generates dynamic links:
Utils.dart
class Utils {
....
static Future<String> generateLink(String sessionId) async {
final DynamicLinkParameters parameters = DynamicLinkParameters(
uriPrefix: AppConfig.dynamicLinkBaseUrl,
link: Uri.parse('${AppConfig.dynamicLinkBaseUrl}/join-session?sessionId=$sessionId'),
dynamicLinkParametersOptions: DynamicLinkParametersOptions(
shortDynamicLinkPathLength: ShortDynamicLinkPathLength.unguessable
),
androidParameters: AndroidParameters(
packageName: 'com.sample.app',
minimumVersion: 0,
),
);
final Uri dynamicUrl = await parameters.buildUrl();
final ShortDynamicLink shortenedLink =
await DynamicLinkParameters.shortenUrl(
dynamicUrl,
DynamicLinkParametersOptions(
shortDynamicLinkPathLength: ShortDynamicLinkPathLength.unguessable),
);
final Uri shortUrl = shortenedLink.shortUrl;
return AppConfig.dynamicLinkBaseUrl + shortUrl.path;
}
...
}
On my home page I have this method that will should be called whenever user lands in the homepage:
HomePage.dart:
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
....
_navigateToDestination() {
DynamicLinkService _service = context.select((DynamicLinkService service) => service);
String sessionToJoin = _service.sessionIdToJoin;
User user = context.read<User>();
if(sessionToJoin != '') {
_service.clearSessionId();
Navigator.pushNamed(context,
AppRoutes.joinStream,
arguments: StreamLiveArguments(
channelName: sessionToJoin,
user: AppUtil.getName(user),
role: ClientRole.Audience
)
);
}
}
#override
initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_navigateToDestination();
});
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(final AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_navigateToDestination();
}
}
....
}
The issue on this implementation is that sometimes it works and sometimes it does not work and I am not sure why. :(
Should I just use a simple class instead of a provider? If yes, how can I persists the sessionId?
Other Scenarios that I want to handle is when user is in different page like the Profile Page.
At this point, user can minimize the app and can click the dynamic link from other sites or from messenger.
When the app resumes, it will open in the ProfilePage where it doesn't have handling for dynamic links (only HomePage and LoginPage has handling on it). How can I remedy this? Should I add handling of dynamic links to all my pages? Or perhaps can I create a dynamic link that can redirect to a certain page in my app? If yes, how can I do that?
I am kinda new in implementing the dynamic links (as well as Providers) and would like your opinion on this issue.Thanks in advance and I hope someone can help me or give me some insights on best practices on how to do this.

Flutter - Firebase Cloud Messaging Navigation in onLaunch doesn't work

I am building an app which receives push notifications using FCM.
I want to route to a specific screen when a notification is clicked (for example, the user's profile).
On Android, it works perfectly fine when the app is just closed (and not "killed"), but when the app is terminated ("killed") it is not working.
On iOS, it doesn't work at all.
I am implementing it life this:
NotificationsHandler:
class NotificationsHandler {
static final NotificationsHandler instance = NotificationsHandler();
final _fcm = FirebaseMessaging();
void onBackgroundNotificationRecevied({Function onReceived}) {
_fcm.configure(
onResume: (message) => onReceived(message),
onLaunch: (message) => onReceived(message),
);
}
}
myMainScreen's initState:
#override
void initState() {
NotificationsHandler.instance.onBackgroundNotificationRecevied(
onReceived: (message) async {
final userId = message['data']['userId'];
final user = this.users.firstWhere((currentUser) => currentUser.id == userId);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserProfileScreen(
user,
),
),
);
}
);
super.initState();
}
Code for sending the notifications (through an external React admin panel):
const payload = {
notification: {
title: `myTitle`,
body: `My message`,
sound: "default",
badge: "1",
click_action: "FLUTTER_NOTIFICATION_CLICK",
},
data: {
click_action: 'FLUTTER_NOTIFICATION_CLICK',
userId: myUserId,
},
};
const options = {
priority: 'high',
timeToLive: 60 * 60 * 24
};
admin.messaging().sendToTopic('myTopic', payload, options);
Does anyone know why it isn't working?
Thank you!
You can try to use getInitialMessage instead of onLaunch. I believe this will do what you want as documentation indicated the following lines:
This should be used to determine whether specific notification interaction should open the app with a specific purpose (e.g. opening a chat message, specific screen etc).
#override
void initState() {
super.initState();
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage message) {
if (message != null) {
Navigator.pushNamed(context, '/message', arguments: MessageArguments(message, true));
}
});
}
I assume that you're using firebase_messaging package.
iOS
If you're testing it on simulator, it won't work. It's stated in the documentation that:
FCM via APNs does not work on iOS Simulators. To receive messages & notifications a real device is required.
Android
On Android, if the user force quits the app from device settings, it must be manually reopened again for messages to start working.
More info here.
Based on my experience, I remember that onLaunch Callback function fires right after execute main function, even before the initstate method.
What I did was locate service class using service locator(e.g get_it) at main function before runApp() then onLaunch Callback set initial configuration so that your App can use it's value.
For example
final getIt = GetIt.instance;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
getIt.registerSingleton<Configurator>(Configurator());///start configuration service
FirebaseMessagingService.initialise()///start firebase messaging service
runApp();
}
...
class FirebaseMessagingService {
final FirebaseMessaging _fcm;
FirebaseMessagingService.initialise() : _fcm = FirebaseMessaging() {
if (Platform.isIOS) {
_fcm.requestNotificationPermissions(IosNotificationSettings());
}
_fcm.configure(
...
onLaunch: _launchMessageHandler,
);
}
}
//top-level function or static method
_launchMessageHandler(Map<String, dynamic> message) async {
//some parsing logic
...
getIt<Configurator>().setInitialConfig(parsed_data);
}
...
//then
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final config = getIt<Configurator>().config;
//do something
}};
You will have to implement those whole settings but it's flow is like above roughly.
I assume your trouble is more towards navigating to another screen upon clicking the notification.
If that is the case create a class for routing.
an example would be as below:
class Navigator{
GlobalKey<NavigatorState> _navigator;
/// Singleton getter
static Navigator get instance => _instance ??= Navigator._();
/// Singleton Holder
static Navigator _instance;
/// Private Constructor
Navigator._() {
_navigator = GlobalKey<NavigatorState>();
}
GlobalKey<NavigatorState> get navigatorKey => _navigator;
Future<dynamic> navigateTo(String routeName, [dynamic arguments]) =>
navigatorKey.currentState.pushNamed(routeName, arguments: arguments);
Now comes the screen/pages
class CustomRoutes {
const CustomRoutes._();
factory CustomRoutes() => CustomRoutes._();
static const String HomeRoute = 'HomeRoute';
...
...
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case CustomRoutes.HomeRoute:
return MaterialPageRoute(builder: (_) => HomePage());
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(child: Text('No path for ${settings.name}'))));
}
}
}
So if u wish to go to HomePage you can just invoke
await Navigator.instance.navigateTo(CustomRoutes.HomeRoute, someArguments)
Do remember to register the globalkey to your materialapp
MaterialApp(
...
...
navigatorKey: Navigator.instance.navigatorKey
...);

How to load app state asynchronously from server while creating store in flutter redux?

Basically I want to load firebase remote config parameters when app starts and store it in app state while creating store.
So that whenever I need it I just fetch it from store and get remoteConfig value.
class App extends StatelessWidget {
// Get firebase remote config and store it in appState
---> final store = createStore();
App();
#override
Widget build(BuildContext context) {
return new PersistorGate(
persistor: persistor,
loading: new LoadingScreen(),
builder: (context) => new StoreProvider<AppState>(
store: store,
child: new MaterialApp(
title: 'TestApp',
theme: defaultTargetPlatform == TargetPlatform.iOS
? kIOSTheme
: kDefaultTheme,
routes: getRoutes(context, store),
initialRoute: '/login',
)),
);
}
}
Create Store file -
Store<AppState> createStore() {
Store<AppState> store = new Store(
appReducer,
// store remote config in initial app state
initialState: new AppState(),
middleware: createMiddleware(),
);
persistor.load(store);
return store;
}
I followed this approach -
Use FutureBuilder
Fetched remote config asynchronously in future
Created store with fetched remoteConfig
I implemented following solution -
Looking for any other possible approach than this.
Edit:
There can be cases when you will not be getting the remote config from firebase or some async operation failed in that case you will have to check if snapshot has data and display some fallback ui.
e.g
if(snapshot.hasData) {
// render component
} else {
// render loader
}
Code:
#override
Widget build(BuildContext context) {
return new FutureBuilder<Store>(
future: setupRemoteConfig(),
builder: (BuildContext context, AsyncSnapshot<Store> snapshot) {
return new PersistorGate(
persistor: persistor,
loading: new LoadingScreen(),
builder: (context) =>
new StoreProvider<AppState>(
store: snapshot.data,
child: new MaterialApp(
title: 'Test App',
theme: defaultTargetPlatform == TargetPlatform.iOS
? kIOSTheme
: kDefaultTheme,
routes: getRoutes(context, snapshot.data),
initialRoute: '/login',
)
),
);
}
);
}
RemoteConfig:
Fetching remoteConfig async way
Future<Store> setupRemoteConfig() async {
final RemoteConfig remoteConfig = await RemoteConfig.instance;
// Enable developer mode to relax fetch throttling
remoteConfig.setConfigSettings(new RemoteConfigSettings(debugMode: true));
remoteConfig.setDefaults(<String, dynamic>{
'welcome': 'default welcome',
});
await remoteConfig.fetch(expiration: const Duration(hours: 5));
await remoteConfig.activateFetched();
return createStore(remoteConfig);
}
Creating Store:
Store<AppState> createStore(RemoteConfig config) {
Store<AppState> store = new Store(
appReducer,
initialState: new AppState(config: config),
middleware: createMiddleware(),
);
persistor.load(store);
return store;
}
Persistor is coming from
import 'package:redux_persist_flutter/redux_persist_flutter.dart';
it used to rehydrate old state in store.

Resources