How to make Flutter with GetX wait until data is loaded - flutter

I'm looking for a best-practice way to implement waiting for my app to initialize data before displaying the first page.
The app has a main controller as well as a controller per page. The main controller initially loads data from a server, and until that's done I'd like to display a splash page (or at least wait before opening the actual app-page)
An simple solution would be, that a page waits for the main controller to be initialized
class MainController extends GetxController {
final isInitialized = false.obs;
#override
void onInit() async {
Future f1 = server.get('service1').then(....)
Future f2 = server.get('service1').then(....)
Future f3 = server.get('service1').then(....)
await Future.wait([f1, f2, f3]);
isInitialized.value = true;
}
}
And a page component could:
class HandleTaskPage extends GetView<HandleTaskPageController> {
#override
Widget build(BuildContext context) {
MainController mainController = Get.find();
return Obx(() {
if (mainController.isInitialized().value) {
return TaskPanelWidget();
} else {
return WaitingPage();
}
})
}
}
But my app allows the user to start at any given page using a direct url (web-app) e.g. http://app.com/showtask/123
Which means that I must put the wait for global controller on every page.
Is there some way I could simply make Get wait (and possibly display a Welcome page) until GlobalController is ready before moving on to the page described in the route?
I've tried to add a WelcomePage to GetMaterialApp, to stop the app from going directly to the requested url. The WelcomeController should then await MainController before redirecting. But even though welcome-page does get rendered, the app still automatically continues to the page requested in the url.
void main() {
runApp(GetMaterialApp(
home: WelcomePage(),
...
...
...

You can implement mixin with name StateMixin.
Example (Controller):
class UserController extends GetxController with StateMixin {
getData() {
// make status to loading
change(null, status: RxStatus.loading());
// Code to get data
await service.getData()
// if done, change status to success
change(null, status: RxStatus.success());
}
}
Example (UI):
class HomePage extends GetView<UserController> {
#override
Widget build(BuildContext context) {
// controller from GetView
return controller.obx((state) {
return OtherWidget()
},
onLoading: CircularProgressIndicator(),
)
}
}

You can use the builder method on MaterialApp to build/show the widget you want.
Using GetX - you can show a loader while waiting for your data to load as follows:
Widget build(BuildContext context) {
final MainController mainController = Get.find();
final isInitialized = mainController.isInitialized().value;
return GetMaterialApp(
title: 'Example',
debugShowCheckedModeBanner: false,
theme: ...,
initialRoute: '/',
getPages: [...],
builder: (context, child) => isInitialized
? Container(child: child)
: const Center(
child: CircularProgressIndicator(color: Colors.blue),
),
);
}
The above should display a blue loader until isInitialized becomes true.

Related

Flutter GetX controller not calling method when revisit the app screen

I am new to the Flutter GetX package and facing problem when using the flutter GetX package. I have several app screens where one screen for listing all the products from the database.
In my product controller, I am fetching all the data from the database and showing with listview like as given code below and It's working fine.
Problem: When I'm inserting a new record from another controller and come back to the product list controller it'll not showing newly added data. Actually this time onInit method won't fire.
Controller code
class ProductIndexCtrl extends GetxController {
var products = [].obs;
#override
void onInit() {
super.onInit();
getAll();
}
void getAll() {
Product.getAll().then((jsonList) {
products.value = Product.fromJsonList(jsonList);
});
}
}
class ProductCreateCtrl extends GetxController {
void saveData(Product product) {
...
...
//after successful insert
Get.toNamed('productIndex');
}
}
Product index screen
final ctrl = Get.put(ProductIndexCtrl());
GetX<ProductIndexCtrl>(builder: (controller) {
return _listview(controller.products);
}
Widget _listview(data) {
...
...
}
As the GetX dependency manager controls the lifecycle of dependencies, you should not manually call them. It's GetX's responsibility when to call them.
Therefore you need to manually call getAll() method of your ProductIndexCtrl controller inside the saveData() method of your ProductCreateCtrl like:
saveData(Product product){
....
....
final indexCtrl= Get.find<ProductIndexCtrl>();
indexCtrl.getAll();
}
By returning to that page, you can return the new information locally to the previous page
> Controller code
class ProductIndexCtrl extends GetxController {
var products = [].obs;
#override
void onInit() {
super.onInit();
getAll();
}
void getAll() {
Product.getAll().then((jsonList) {
products.value = Product.fromJsonList(jsonList);
});
}
}
> Product index screen
class ProductCreateCtrl extends GetxController {
void saveData(Product product) {
...
...
//after successful insert
Get.back(result: product);
}
}
and get Data when back
Get.toName('ProductCreateCtrl').then(result){
products.add(result);
}
I tried a similar thing with one single controller. The code snippet is given below.
First, create the ProductView. Since this is the entry point of the application, So you will create a GetX controller inside of this.
/// THIS IS PARENT VIEW SO WE WILL CREATE GETX CONTROLLER HERE
class ProductView extends StatelessWidget {
const ProductView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final controller = Get.put(ProductController());
return Scaffold(
appBar: AppBar(
title: const Text('Products'),
),
body: Obx(() {
return controller.myProductList.isEmpty
? showNoProductView()
: ListView.builder(
itemCount: controller.myProductList.length,
itemBuilder: (context, index) {
return YourListItemView(controller.myProductList[index]);
},
);
}),
);
}
}
The view AddProductView is responsible for adding new products to the DB. We can assume that there is a FloatingActionButton present in ProductView and onClick on that button, we will open this AddProductView.
/// THIS IS CHILD VIEW SO WE WILL FIND THE PRODUCT CONTROLLER HERE
class AddProductView extends StatelessWidget {
const AddProductView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final controller = Get.find<ProductController>();
return Scaffold(
appBar: AppBar(
title: const Text('Add Product'),
),
body: Column(
children: [
// ADD YOUR OTHER WIDGETS TO GET PRODUCT INFO
TextButton(
child: const Text('Click to Add'),
onPressed: () {
var productName = nameTextEditingController.text;
var productQuantity = qtyTextEditingController.text;
var product = YourProductObject(productName, productQuantity);
controller.addProduct(product: product);
},
)
],
),
);
}
}
Finally, the controller will look like this.
import 'package:get/get.dart';
class ProductController extends GetxController {
// this will be your custom product list object
var myProductList = <YourProductObject>[].obs;
var dbInstance = YourDbInstance();
#override
void onReady() async {
super.onReady();
// perform database operation
await fetchDataFromDb();
}
Future<void> fetchDataFromDb() async {
// assuming that data is coming as List<YourProductListObject>
// always use try catch in db operation. for demo purpose I am skipping that.
var productListFromDb = await dbInstance.getYourProductListObjectList();
myProductList.assignAll(productListFromDb);
}
Future<void> addProduct({required YourProductObject product}) async {
// assuming that there is a function that returns true if a product is added to db
var isAdded = await dbInstance.addProduct(product);
if (isAdded) {
myProductList.add(product);
}
}
}
Since myProductList is a RxList so getx will observe it and will update the UI accordingly. You must add Obx((){}) in view.

Flutter - Using GetIt with BuildContext

I'm using Localizations in my app based on the flutter documentation.
See here: https://flutter.dev/docs/development/accessibility-and-localization/internationalization
I use get_it package (version 4.0.4) to retrieve singleton objects like the Localization delegate. Unfortunately it needs a BuildContext property. Sometimes in my app I don't have the context reference so it would be nice if it would work like this: GetIt.I<AppLocalizations>() instead of this: AppLocalizations.of(context). It still can be achieved without a problem if you setup get_it like this: GetIt.I.registerLazySingleton(() => AppLocalizations.of(context)); The problem is that you need the context at least once to make it work. Moreover if you would like to display a localized text instantly in your initial route it's more difficult to get a properly initialized BuildContext at a time when you need it.
It's a little hard for me to explain it properly so I recreated the issue in a minimal example.
I commented out some code that would cause compile time errors, but it shows how I imagined it to be done.
main.dart
GetIt getIt = GetIt.instance;
void setupGetIt() {
// How to get BuildContext properly if no context is available yet?
// Compile time error.
// getIt.registerLazySingleton(() => AppLocalizations.of(context));
}
void main() {
setupGetIt();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
// The above line also won't work. It has BuildContext but Applocalizations.of(context) won't work
// because it's above in the Widget tree and not yet setted up.
getIt.registerLazySingleton(() => AppLocalizations.of(context));
return MaterialApp(
supportedLocales: const [
Locale('en', 'US'),
Locale('hu', 'HU'),
],
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
localeResolutionCallback: (locale, supportedLocales) {
// check if locale is supported
for (final supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale?.languageCode &&
supportedLocale.countryCode == locale?.countryCode) {
return supportedLocale;
}
}
// if locale is not supported then return the first (default) one
return supportedLocales.first;
},
// You may pass the BuildContext here for Page1 in it's constructor
// but in a more advanced routing case it's not a maintanable solution.
home: Page1(),
);
}
}
Initial route
class PageBase extends StatelessWidget {
final String title;
final Widget content;
PageBase(this.title, this.content);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: content,
);
}
}
class Page1 extends PageBase {
// It won't run because I need the context but clearly I don't have it.
// And in a real app you also don't want to pass the context all over the place
if you have many routes to manage.
Page1(String title)
: super(AppLocalizations.of(context).title, Center(child: Text('Hello')));
// Intended solution
// I don't know how to properly initialize getIt AppLocalizations singleton by the time
// it tries to retrieve it
Page1.withGetIt(String title)
: super(getIt<AppLocalizations>().title, Center(child: Text('Hello')));
}
locales.dart
String globalLocaleName;
class AppLocalizations {
//AppLocalizations(this.localeName);
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
static Future<AppLocalizations> load(Locale locale) async {
final String name =
locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
final String localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
globalLocaleName = localeName;
return AppLocalizations();
});
}
String get title => Intl.message(
'This is the title.',
name: 'title',
);
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
// This delegate instance will never change (it doesn't even have fields!)
// It can provide a constant constructor.
const _AppLocalizationsDelegate();
#override
bool isSupported(Locale locale) {
return ['en', 'hu'].contains(locale.languageCode);
}
#override
Future<AppLocalizations> load(Locale locale) => AppLocalizations.load(locale);
#override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
And some intl generated dart code and .arb files that is not so important to illustrate the problem.
So all in all, how can I achive to use my AppLocalizations class as a singleton without using a context for example in a situation like this? Maybe my initial approach is bad and it can be done in other ways that I represented. Please let me know if you have a solution.
Thank you.
To achieve what you have described you need to first make the navigation service using get_it. Follow these steps to achieve the result :
1. Create a navigation service
import 'package:flutter/material.dart';
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey =
new GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState!
.push(routeName);
}
goBack() {
return navigatorKey.currentState!.pop();
}
}
This allows you to navigate anywhere from any point throughout the app without build context. This navigator key is what you can use to achieve the AppLocalization instance for the current context.
Refer to the FilledStacks tutorials for this method of navigating without build context.
https://www.filledstacks.com/post/navigate-without-build-context-in-flutter-using-a-navigation-service/
2. Register
GetIt locator = GetIt.instance;
void setupLocator() {
...
locator.registerLazySingleton(() => NavigationService());
...
}
3. Assign the navigator key in the material app
return MaterialApp(
...
navigatorKey: navigationService.navigatorKey,
...
),
3. Create an instance for the AppLocalizations and import it wherever you want to use
localeInstance() => AppLocalizations.of(locator<NavigationService>().navigatorKey.currentContext!)!;
3. The actual use case
import 'package:{your_app_name}/{location_to_this_instace}/{file_name}.dart';
localeInstance().your_localization_variable
You can add a builder to your MaterialApp and setup the service locator inside it with the context available. Example:
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, widget) {
setUpServiceLocator(context);
return FutureBuilder(
future: getIt.allReady(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return widget;
} else {
return Container(color: Colors.white);
}
});
},
);
}
Service Locator Setup:
void setUpServiceLocator(BuildContext context) {
getIt.registerSingleton<AppLocalizations>(AppLocalizations.of(context));
}
You could use some non-localizable splash screen with FutureBuilder and getIt.allReady().
Something like:
class SplashScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder<void>(
future: getIt.allReady(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// Navigate to main page (with replace)
} else if (snapshot.hasError) {
// Error handling
} else {
// Some pretty loading indicator
}
},
);
}
I'd like to recommend the injectable package for dealing with get_it also.

Change state in one Widget from another widget

I'm programming a flutter application in which a user is presented with a PageView widget that allows him/her to navigate between 3 "pages" by swiping.
I'm following the setup used in https://medium.com/flutter-community/flutter-app-architecture-101-vanilla-scoped-model-bloc-7eff7b2baf7e, where I use a single class to load data into my model, which should reflect the corresponding state change (IsLoadingData/HasData).
I have a main page that holds all ViewPage widgets. The pages are constructed in the MainPageState object like this:
#override
void initState() {
_setBloc = SetBloc(widget._repository);
_notificationBloc = NotificationBloc(widget._repository);
leftWidget = NotificationPage(_notificationBloc);
middleWidget = SetPage(_setBloc);
currentPage = middleWidget;
super.initState();
}
If we go into the NotificationPage, then the first thing it does is attempt to load data:
NotificationPage(this._notificationBloc) {
_notificationBloc.loadNotificationData();
}
which should be reflected in the build function when a user directs the application to it:
//TODO: Consider if state management is correct
#override
Widget build(BuildContext context) {
return StreamBuilder<NotificationState>(
stream: _notificationBloc.notification.asBroadcastStream(),
//initialData might be problematic
initialData: NotificationLoadingState(),
builder: (context, snapshot) {
if (snapshot.data is NotificationLoadingState) {
return _buildLoading();
}
if (snapshot.data is NotificationDataState) {
NotificationDataState state = snapshot.data;
return buildBody(context, state.notification);
} else {
return Container();
}
},
);
}
What happens is that the screen will always hit "NotificationLoadingState" even when data has been loaded, which happens in the repository:
void loadNotificationData() {
_setStreamController.sink.add(NotificationState._notificationLoading());
_repository.getNotificationTime().then((notification) {
_setStreamController.sink
.add(NotificationState._notificationData(notification));
print(notification);
});
}
The notification is printed whilst on another page that is not the notification page.
What am i doing wrong?
//....
class _SomeState extends State<SomeWidget> {
//....
Stream<int> notificationStream;
//....
#override
void initState() {
//....
notificationStream = _notificationBloc.notification.asBroadcastStream()
super.initState();
}
//....
Widget build(BuildContext context) {
return StreamBuilder<NotificationState>(
stream: notificationStream,
//....
Save your Stream somewhere and stop initialising it every time.
I suspect that the build method is called multiple times and therefore you create a new stream (initState is called once).
Please try let me know if this helped.

How to go back and refresh the previous page in Flutter?

I have a home page which when clicked takes me to another page through navigates, do some operations in then press the back button which takes me back to the home page. but the problem is the home page doesn't get refreshed.
Is there a way to reload the page when i press the back button and refreshes the home page?
You can trigger the API call when you navigate back to the first page like this pseudo-code
class PageOne extends StatefulWidget {
#override
_PageOneState createState() => new _PageOneState();
}
class _PageOneState extends State<PageOne> {
_getRequests()async{
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new RaisedButton(onPressed: ()=>
Navigator.of(context).push(new MaterialPageRoute(builder: (_)=>new PageTwo()),)
.then((val)=>val?_getRequests():null),
),
));
}
}
class PageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
//somewhere
Navigator.pop(context,true);
}
}
Or you can just use a stream if the API is frequently updated, the new data will be automatically updated inside your ListView
For example with firebase we can do this
stream: FirebaseDatabase.instance.reference().child(
"profiles").onValue
And anytime you change something in the database (from edit profile page for example), it will reflect on your profile page. In this case, this is only possible because I am using onValue which will keep listening for any changes and do the update on your behalf.
(In your 1st page): Use this code to navigate to the 2nd page.
Navigator.pushNamed(context, '/page2').then((_) {
// This block runs when you have returned back to the 1st Page from 2nd.
setState(() {
// Call setState to refresh the page.
});
});
(In your 2nd page): Use this code to return back to the 1st page.
Navigator.pop(context);
use result when you navigate back from nextScreen as follow :
Navigator.of(context).pop('result');
or if you are using Getx
Get.back(result: 'hello');
and to reload previous page use this function :
void _navigateAndRefresh(BuildContext context) async {
final result = await Get.to(()=>NextScreen());//or use default navigation
if(result != null){
model.getEMR(''); // call your own function here to refresh screen
}
}
call this function instead of direct navigation to nextScreen
The solution which I found is simply navigating to the previous page:
In getx:
return WillPopScope(
onWillPop: () {
Get.off(() => const PreviousPage());
return Future.value(true);
},
child: YourChildWidget(),
or if you want to use simple navigation then:
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) =>PreviousPage() ,));
Simply i use this:
onPressed: () {
Navigator.pop(context,
MaterialPageRoute(builder: (context) => SecondPage()));
},
this to close current page:
Navigator.pop
to navigate previous page:
MaterialPageRoute(builder: (context) => SecondPage())
In FirtsPage, me adding this for refresh on startUpPage:
#override
void initState() {
//refresh the page here
super.initState();
}
For a more fine-grained, page-agnostic solution I came up with this Android Single LiveEvent mimicked behaviour.
I create such field inside Provider class, like:
SingleLiveEvent<int> currentYearConsumable = SingleLiveEvent<int>();
It has a public setter to set value. Public consume lets you read value only once if present (request UI refresh). Call consume where you need (like in build method).
You don't need Provider for it, you can use another solution to pass it.
Implementation:
/// Useful for page to page communication
/// Mimics Android SingleLiveEvent behaviour
/// https://stackoverflow.com/questions/51781176/is-singleliveevent-actually-part-of-the-android-architecture-components-library
class SingleLiveEvent<T> {
late T _value;
bool _consumed = true;
set(T val) {
_value = val;
_consumed = false;
}
T? consume() {
if (_consumed) {
return null;
} else {
_consumed = true;
return _value;
}
}
}
await the navigation and then call the api function.
await Navigator.of(context).pop();
await api call
You can do this with a simple callBack that is invoked when you pop the route. In the below code sample, it is called when you pop the route.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
_someFunction()async{
Navigator.of(context).push(MaterialPageRoute(builder: (_)=> PageTwo(
onClose():(){
// Call setState here to rebuild this widget
// or some function to refresh data on this page.
}
)));
}
#override
Widget build(BuildContext context) {
return SomeWidget();
}
...
} // end of widget
class PageTwo extends StatelessWidget {
final VoidCallback? onClose;
PageTwo({Key? key, this.onClose}) : super(key: key);
#override
Widget build(BuildContext context) {
return SomeWidget(
onEvent():{
Navigate.of(context).pop();
onClose(); // call this wherever you are popping the route
);
}
}

Flutter - How to pass user data to all views

I'm new to the flutter world and mobile app development and struggling with how I should pass user data throughout my app.
I've tried several things, but none seem great and I'm sure there are best practice patterns I should be following.
Because it makes examples easier, I'm using firebase for authentication.
I currently have a separate route for logging in. Once I'm logged in I want the User model in most views for checking permissions on what to show, displaying user info in the drawer, etc...
Firebase has an await firebaseAuth.currentUser(); Is it best practice to call this everywhere you might need the user? and if so, where is the best spot to place this call?
The flutter codelab shows a great example of authenticating users before allowing writes. However, if the page needs to check auth to determine what to build, the async call can't go in the build method.
initState
One method I've tried is to override initState and kick off the call to get the user. When the future completes I call setState and update the user.
FirebaseUser user;
#override
void initState() {
super.initState();
_getUserDetail();
}
Future<Null> _getUserDetail() async {
User currentUser = await firebaseAuth.currentUser();
setState(() => user = currentUser);
}
This works decent but seems like a lot of ceremony for each widget that needs it. There is also a flash when the screen loads without the user and then gets updated with the user upon the future's completion.
Pass the user through the constructor
This works too but is a lot of boilerplate to pass the user through all routes, views, and states that might need to access them. Also, we can't just do popAndPushNamed when transitioning routes because we can't pass a variable to it. We have to change routes similar to this:
Navigator.push(context, new MaterialPageRoute(
builder: (BuildContext context) => new MyPage(user),
));
Inherited Widgets
https://medium.com/#mehmetf_71205/inheriting-widgets-b7ac56dbbeb1
This article showed a nice pattern for using InheritedWidget. When I place the inherited widget at the MaterialApp level, the children aren't updating when the auth state changed (I'm sure I'm doing it wrong)
FirebaseUser user;
Future<Null> didChangeDependency() async {
super.didChangeDependencies();
User currentUser = await firebaseAuth.currentUser();
setState(() => user = currentUser);
}
#override
Widget build(BuildContext context) {
return new UserContext(
user,
child: new MaterialApp(
title: 'TC Stream',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new LoginView(title: 'TC Stream Login', analytics: analytics),
routes: routes,
),
);
}
FutureBuilder
FutureBuilder also seems like a decent option but seems to be a lot of work for each route. In the partial example below, _authenticateUser() is getting the user and setting state upon completion.
#override
Widget build(BuildContext context) {
return new FutureBuilder<FirebaseUser>(
future: _authenticateUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return _buildProgressIndicator();
}
if (snapshot.connectionState == ConnectionState.done) {
return _buildPage();
}
},
);
}
I'd appreciate any advice on best practice patterns or links to resources to use for examples.
I'd recommend investigating inherited widgets further; the code below shows how to use them with asynchronously updating data:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(new MaterialApp(
title: 'Inherited Widgets Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text('Inherited Widget Example'),
),
body: new NamePage())));
}
// Inherited widget for managing a name
class NameInheritedWidget extends InheritedWidget {
const NameInheritedWidget({
Key key,
this.name,
Widget child}) : super(key: key, child: child);
final String name;
#override
bool updateShouldNotify(NameInheritedWidget old) {
print('In updateShouldNotify');
return name != old.name;
}
static NameInheritedWidget of(BuildContext context) {
// You could also just directly return the name here
// as there's only one field
return context.inheritFromWidgetOfExactType(NameInheritedWidget);
}
}
// Stateful widget for managing name data
class NamePage extends StatefulWidget {
#override
_NamePageState createState() => new _NamePageState();
}
// State for managing fetching name data over HTTP
class _NamePageState extends State<NamePage> {
String name = 'Placeholder';
// Fetch a name asynchonously over HTTP
_get() async {
var res = await http.get('https://jsonplaceholder.typicode.com/users');
var name = json.decode(res.body)[0]['name'];
setState(() => this.name = name);
}
#override
void initState() {
super.initState();
_get();
}
#override
Widget build(BuildContext context) {
return new NameInheritedWidget(
name: name,
child: const IntermediateWidget()
);
}
}
// Intermediate widget to show how inherited widgets
// can propagate changes down the widget tree
class IntermediateWidget extends StatelessWidget {
// Using a const constructor makes the widget cacheable
const IntermediateWidget();
#override
Widget build(BuildContext context) {
return new Center(
child: new Padding(
padding: new EdgeInsets.all(10.0),
child: const NameWidget()));
}
}
class NameWidget extends StatelessWidget {
const NameWidget();
#override
Widget build(BuildContext context) {
final inheritedWidget = NameInheritedWidget.of(context);
return new Text(
inheritedWidget.name,
style: Theme.of(context).textTheme.display1,
);
}
}
I prefer to use Services with Locator, using Flutter get_it.
Create a UserService with a cached data if you like:
class UserService {
final Firestore _db = Firestore.instance;
final String _collectionName = 'users';
CollectionReference _ref;
User _cachedUser; //<----- Cached Here
UserService() {
this._ref = _db.collection(_collectionName);
}
User getCachedUser() {
return _cachedUser;
}
Future<User> getUser(String id) async {
DocumentSnapshot doc = await _ref.document(id).get();
if (!doc.exists) {
log("UserService.getUser(): Empty companyID ($id)");
return null;
}
_cachedUser = User.fromDocument(doc.data, doc.documentID);
return _cachedUser;
}
}
Then create create a Locator
GetIt locator = GetIt.instance;
void setupLocator() {
locator.registerLazySingleton(() => new UserService());
}
And instantiate in main()
void main() {
setupLocator();
new Routes();
}
That's it! You can call your Service + cachedData everywhere using:
.....
UserService _userService = locator<UserService>();
#override
void initState() {
super.initState();
_user = _userService.getCachedUser();
}
I crashed into another problem because of this problem you can check it out here
So the solution I came up with is a bit untidy,I created a separate Instance dart page and imported it to every page.
GoogleSignInAccount Guser = googleSignIn.currentUser;
FirebaseUser Fuser;
I stored the user there on login and checked on every StateWidget if it was null
Future<Null> _ensureLoggedIn() async {
if (Guser == null) Guser = await googleSignIn.signInSilently();
if (Fuser == null) {
await googleSignIn.signIn();
analytics.logLogin();
}
if (await auth.currentUser() == null) {
GoogleSignInAuthentication credentials =
await googleSignIn.currentUser.authentication;
await auth.signInWithGoogle(
idToken: credentials.idToken,
accessToken: credentials.accessToken,
);
}
This is my old code I did cleaned it up on my current app but I don't have that code now in handy. Just check out for null user and log it in again
I did it for most of the Firebase instances too because I have more than 3 pages on my app and Inherited Widgets was just too much work
You can use the GetX package to check whether or not the user is logged in, get user data and have it accessible throughout your app
For my lazy mathod,
i just create new file like userdata.dart and then put any variable on it for example like dynamic Profile = null
inside userdata.dart
//only put this or anything u want.
dynamic Profile = null;
at startingpage.dart
//import that file
import '../userdata.dart';
class startingpage extends ...{
...
//set data to store..
Profile = 'user profile';
...
}
to use the data just declare and use in
anotherpage.dart
//import that file
import '../userdata.dart';
class anotherpage extends...{
...
}
class .. State ...{
...
//set the data to variable
dynamic userdata = Profile;
print('this is my lazy pass data' + userdata.toString());
...
}