Flutter - Using GetIt with BuildContext - flutter

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.

Related

Provider to be initialized asynchronously from `initState()` but get `could not find the correct Provider`

I develop an ad app, with a message button on the detailed view.
When the user tap on it, the chats view (stateful widget) is pushed to the screen.
The initState() is there to call the asyncInitMessages() which asynchronously fetches the chats and related message from the distant database. The asyncInitMessages() belongs to the Chats class which extends ChangeNotifier.
/// A chat conversation
class Chats extends ChangeNotifier {
/// Internal, private state of the chat.
void asyncInitMessages(
{required ClassifiedAd ad,
required String watchingUserId,
required bool isOwner}) async {
// blah blah
}
}
The ClassifiedAdMessagesViewstateful widget class implementation is as follows (snipet):
#override
void initState() {
// == Fetch conversation and messages
asyncInitMessages();
}
void asyncInitMessages() async {
// === Update all messages
try {
Provider.of<Chats>(context, listen: false).asyncInitMessages(
ad: widget.ad,
watchingUserId: widget.watchingUser!.uid,
isOwner: _isOwner);
} catch (e) {
if (mounted) {
setState(() {
_error = "$e";
_ready = true;
});
}
}
}
#override
Widget build(BuildContext context) {
// <<<<<<<<<<< The exception fires at the Consumer line right below
return Consumer<Chats>(builder: (context, chats, child) {
return Scaffold(
// ... blah blah
Finally, when running ll that, I got the exception in the build at the Consumer line:
could not find the correct Provider<chats>
Help greatly appreciated.
[UPDATED]
Here is the main (very far up from the messages screen)
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
//if (Firebase.apps.isEmpty) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// } else {
// Firebase.app(); // if already initialized, use that one
// }
if (USE_DATABASE_EMULATOR) {
FirebaseDatabase.instance.useDatabaseEmulator(emulatorHost, emulatorPort);
}
runApp(RootRestorationScope(
restorationId: 'root',
child: ChangeNotifierProvider(
create: (context) => StateModel(),
child: const App())));
}
class App extends StatefulWidget {
const App({super.key});
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
Widget build(BuildContext context) {
return PersistedAppState(
storage: const JsonFileStorage(),
child: MultiProvider(
providers: [
ChangeNotifierProvider<ThemeModel>.value(value: _themeModel),
//ChangeNotifierProvider<AuthModel>.value(value: _auth),
],
child: Consumer<ThemeModel>(
builder: (context, themeModel, child) => MaterialApp(
// blah blah
}
}
}
And the component just on top of the
/// Classified ad detail view
class ClassifiedAdDetailView extends StatefulWidget {
final User? watchingUser;
final ClassifiedAd ad;
const ClassifiedAdDetailView(
{Key? key, required this.watchingUser, required this.ad})
: super(key: key);
#override
State<ClassifiedAdDetailView> createState() => _ClassifiedAdDetailViewState();
}
class _ClassifiedAdDetailViewState extends State<ClassifiedAdDetailView>
with TickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => Chats(),
builder: ((context, child) => Scaffold(
// blah blah
ElevatedButton(
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ClassifiedAdMessagesView(
ad: ad,
watchingUser: widget.watchingUser)));
}),
Providers must be located in the widget tree above the widget where you want to use them with Consumer or Provider.of. When you push a new route with Navigator, it won't be add the pushed route below the widget from where you push, it will add it at the same level where home of MaterialApp is located.
(I think the error message you get also states that you can't access the providers between routes.)
In general the tree will look like this if you push some routes (check it with the Flutter Widget Inspector):
MaterialApp
home
widget1
widget2
widget21
widget22
page1
widget1
widget2
page2
page3
In your code you create the provider in ClassifiedAdDetailView and then push
ClassifiedAdMessagesView from this in the onPressed method. You won't be access this provider from ClassifiedAdMessagesView because the tree will be like (simplified):
MaterialApp
home
ClassifiedAdDetailView
ClassifiedAdMessagesView
The solution is to "lift the state up" and place the provider above every widget from where you need to access it. It can be a part of your existing Multiprovider above MaterialApp but if it is too far, you need to find a proper place that is above both ClassifiedAdDetailView and ClassifiedAdMessagesView.

How to set ThemeMode in splash screen using value stored in sqflite FLUTTER

I have a Flutter Application where an sqflite database stored the user preference of ThemeMode (viz Dark, Light and System). I have created a splash screen using flutter_native_splash which supports dark mode too.
The Problem is this that I want the splash screen to follow the users stored value for theme mode. Currently, the code I am using is as follows:
class MyRoot extends StatefulWidget {
// const MyRoot({Key? key}) : super(key: key);
static ValueNotifier<ThemeMode> themeNotifier = ValueNotifier(ThemeMode.system);
#override
State<MyRoot> createState() => _MyRootState();
}
class _MyRootState extends State<MyRoot> {
DatabaseHelper? databaseHelper = DatabaseHelper.dhInstance;
ThemeMode? tmSaved;
#override
void initState() {
Future.delayed(Duration.zero, () async => await loadData());
super.initState();
}
#override
Widget build(BuildContext context) {
//to prevent auto rotation of the app
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
return ValueListenableBuilder<ThemeMode>(
valueListenable: MyRoot.themeNotifier,
builder: (_, ThemeMode currentMode, __) {
return Sizer(
builder: (context, orientation, deviceType) {
return MaterialApp(
title: 'My Application',
theme: themeLight, //dart file for theme
darkTheme: themeDark, //dart file for theme
themeMode: tmSaved ?? currentMode,
initialRoute: // my initial root
routes: {
// my routes
.
.
.
// my routes
},
);
},
);
},
);
}
Future<void> loadData() async {
if (databaseHelper != null) {
ThemeMode? themeMode= await databaseHelper?.selectStoredTheme(); // function retrieving sqflite stored value and returning ThemeMode value
if (themeMode != null) {
MyRoot.themeNotifier.value = themeMode;
return;
}
}
MyRoot.themeNotifier.value = ThemeMode.system;
}
}
Currently, this shows a light theme splash screen loading, then converts it into dark with a visible flicker.
ValueListenableBuilder<ThemeMode>(... is to enable real time theme change from settings page in my app which working as intended (taken from A Goodman's article: "Flutter: 2 Ways to Make a Dark/Light Mode Toggle".
main.dart has the below code:
void main() {
runApp(MyRoot());
}
Have you tried loading the setting from sqflite in main() before runApp? If you can manage to do so, you should be able to pass the setting as argument to MyRoot and then the widgets would be loaded from the start with the correct theme. I'm speaking in theory, I can't test what I'm suggesting right now.
Something like:
void main() async {
ThemeMode? themeMode= await databaseHelper?.selectStoredTheme(); // function retrieving sqflite stored value and returning ThemeMode value
runApp(MyRoot(themeMode));
}
[...]
class MyRoot extends StatefulWidget {
ThemeMode? themeMode;
const MyRoot(this.themeMode, {Key? key}) : super(key: key);
static ValueNotifier<ThemeMode> themeNotifier = ValueNotifier(ThemeMode.system);
#override
State<MyRoot> createState() => _MyRootState();
}
EDIT
Regarding the nullable value you mentioned in comments, you can change the main like this:
void main() async {
ThemeMode? themeMode= await databaseHelper?.selectStoredTheme(); // function retrieving sqflite stored value and returning ThemeMode value
themeMode ??= ThemeMode.system;
runApp(MyRoot(themeMode!));
}
which makes themeMode non-nullable, and so you can change MyRoot in this way:
class MyRoot extends StatefulWidget {
ThemeMode themeMode;
const MyRoot(required this.themeMode, {Key? key}) : super(key: key);
[...]
}
Regarding the functionality of ValueNotifier, I simply thought of widget.themeMode as the initial value of your tmSaved property in your state, not as a value to be reused in the state logic. Something like this:
class _MyRootState extends State<MyRoot> {
DatabaseHelper? databaseHelper = DatabaseHelper.dhInstance;
late ThemeMode tmSaved;
#override
void initState() {
tmSaved = widget.themeMode;
super.initState();
}
[...]
}
so that your widgets would already have the saved value at the first build.
PS the code in this edit, as well as in the original part, isn't meant to be working by simply pasting it. Some things might need adjustments, like adding final to themeMode in MyRoot.
Make your splashscreen. A main widget which get data from sqlflite
And make splashscreen widget go to the your home widget with remove it using navigation pop-up
for example :
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'ToDo',
color: // color of background
theme: // theme light ,
darkTheme: // darktheme
themeMode: // choose default theme light - dark - system
home: Splashscreen(),// here create an your own widget of splash screen contains futurebuilder to fecth data and return the mainWidget ( home screen for example)
);
}
}
class Splashscreen extends StatelessWidget {
Future<bool> getData()async{
// get info
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(),
builder: (context,snapshot){
// if you want test snapshot
//like this
if(snapshot.hasData) {
return Home();
} else {
return Container(color: /* background color as same as theme's color */);
}
}
);
}
}

Flutter ViewModel old data is repopulating (using Provider)

I'm using multiProvider to manage state in flutter. The main problem that i'm facing is;
Unable to clear the data inside my viewModel after closing a page. When i open the page again, old data is populating from viewModel.
Because of this issue i've created a 'reset' method in my viewModel & calling this 'reset' method before open the page.
Is there any way to remove old values from viewModel :- Please suggest
My code:
Main Page
Adding providers under multi provider
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: ViewModel1()),
ChangeNotifierProvider.value(value: ViewModel2()),
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
),
//Set Home Page after splash
home: HomePage(),
)
);
}
}
This is my Homepage Navigate to FirstPage & reset ViewModel1 data
HomePage()
{
:
:
:
onPressed(){
Navigator.push(
context, MaterialPageRoute(builder: (context) => FirstPage()));
Provider.of<ViewModel1>(context, listen: false).reset();
}
}
This is My Page:
class FirstPage extends StatefulWidget {
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
Widget build(BuildContext context) {
final ViewModel1 _viewModel1 =
Provider.of<ViewModel1>(context, listen: true);
TextFormField(
controller: _fNameController,
onChanged: _viewModel1.setFname,
);
}
}
This is my ViewModel:
class ViewModel1 with ChangeNotifier {
String _fName = '';
String get fName => _fName;
setFname(String fName) {
_fName = fName;
notifyListeners();
setFnameValidation(fName
.trim()
.isNotEmpty ? true : false);
}
//TODO Reset all values
void reset() {
_fName = '';
notifyListeners();
}
}
If you are reusing one model on different pages, without sharing data between pages.. It's better to create and close the model on every page.

Riverpod ProviderListener - 'StateNotifierProvider<Auth, bool>' can't be assigned to 'ProviderBase<Object, StateController<bool>>'

I'm trying to use a ProviderListener from Riverpod to listen to my authProvider and control the page displayed if a user is authorized or not. I'm getting the error:
error: The argument type 'StateNotifierProvider<Auth, bool>' can't be assigned to the parameter type 'ProviderBase<Object, StateController>'.
The error shows up on the: provider: authProvider, inside the ProviderListener
I'm wondering if it's due to the update on StateNotifierProvider?
I would like to know how to use the ProviderListener better even if there's a better way to handle the authorization flow (I'm VERY open to feedback and criticism and greatly appreciate any time a person can take to help). I cut out non-relevant code
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class Auth extends StateNotifier<bool> {
Auth() : super(false);
void setAuth(bool auth) {
state = auth;
}
}
final authProvider = StateNotifierProvider<Auth, bool>((ref) => Auth());
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatefulHookWidget {
// const MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final Future<FirebaseApp> _fbMyApp = Firebase.initializeApp();
Widget route = SplashScreen();
#override
Widget build(BuildContext context) {
return ProviderListener<StateController<bool>>(
provider: authProvider,
onChange: (context, auth) {
if (auth.state = true) {
route = HomeScreen();
} else {
route = SplashScreen();
}
},
child: MaterialApp(
home: route,
);
}
}
I managed to get it to sort of work by changing to:
return ProviderListener<StateNotifier<bool>>(
provider: authProvider.notifier,
it's giving me a non-breaking error of:
info: The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'. (invalid_use_of_protected_member)
and not working properly - the state isn't being updated when I'm using a context.read
context.read(authProvider.notifier).state = true;
So it's buggy but not fully broken. At least it's some progress. I would still love help and any feedback anyone wants to give!
Remove StateController from ProviderListener, leave only the type (bool in this case)
return ProviderListener<bool>(
provider: authProvider, //this will read the state of your provider (a bool state)
onChange: (context, auth) {
if (auth) { //remove setter auth = true, it doesn't make sense to set a value inside an if
route = HomeScreen();
} else {
route = SplashScreen();
}
},
child: MaterialApp(
home: route,
);
This way you're reading the state of your StateNotifier

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());
...
}