I ended up with using a static function but I need to do navigation and It gave me an error that no getter was found for context so I looked for a solution and found the GET package but when I tried to use it It gave me another error :
E/flutter ( 6078): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)]
Unhandled Exception: NoSuchMethodError: The method 'push' was called on null.
My code:
void main() {
runApp(MyApp());
_MyAppState.autologin();
}
class _MyAppState extends State<MyApp> {
static autologin() async {
var userType;
var store = Firestore.instance;
var auth = FirebaseAuth.instance;
final FirebaseUser user = await auth.currentUser();
store.collection('Users').document(user.uid).get().then((value) {
userType = (value.data)['userType'];
if (userType == 'Student') {
Get.to(StudentsPage());
} else if (userType == 'Teacher') {
} else if (userType == 'Admin') {}
});
}
Create a navigator key
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Assign it to MaterialApp
MaterialApp(
home: Home(),
navigatorKey: navigatorKey
),
Then push your routes by navigatorKey below
navigatorKey.currentState.push(MaterialPageRoute(
builder: (context) => AnotherPage(),
));
or
navigatorKey.currentState.pushNamed(routeName);
This solution is general if you want to navigate or to show Dialog without context using globalKey especially with Bloc or when your logic is separated from your UI part.
Firstly install this package:
Note: I'm using null safety version
get_it: ^7.2.0
Then create a separate file for your service locator:
service_location.dart
import 'package:get_it/get_it.dart';
GetIt locator = GetIt.instance;
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey =
new GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(String routeName) {
return navigatorKey.currentState!.pushNamed(routeName);
}
void setupLocator() {
locator.registerLazySingleton(() => NavigationService());
}
void showMyDialog() {
showDialog(
context: navigatorKey.currentContext!,
builder: (context) => Center(
child: Material(
color: Colors.transparent,
child: Text('Hello'),
),
));
}
}
on main.dart:
void main() {
WidgetsFlutterBinding.ensureInitialized();
NavigationService().setupLocator();
runApp(MyApp());
}
// add navigatorKey for MaterialApp
MaterialApp(
navigatorKey: locator<NavigationService>().navigatorKey,
),
at your business logic file bloc.dart
define this inside the bloc class or at whatever class you want to use navigation inside
Then start to navigate inside any function inside.
class Cubit extends Cubit<CubitState> {
final NavigationService _navigationService = locator<NavigationService>();
void sampleFunction(){
_navigationService.navigateTo('/home_screen'); // to navigate
_navigationService.showMyDialog(); // to show dialog
}
}
Not: I'm using generateRoute for routing.
Related
I'm using go_router for navigation and flutter_bloc for my state management.
I would like to use refreshListenable and listen to my AuthBloC events in my GoRouter configuration, however, I don't have access to context in my GoRouter, and I would like not to provide it.
most of the answers I've found are outdated or use alternatives like StreamBuilder to re-render the whole app on event changes which are not ideal.
How can I listen to my AuthEvents outside of context?
main.dart
return RepositoryProvider.value(
value: authRepo,
child: BlocProvider(
create: (_) => AuthBloc(authRepo),
child: MaterialApp.router(
...
routerConfig: Routes.router,
builder: (_, child) {
final isRTL = LocaleSettings.currentLocale == AppLocale.ar;
return Directionality(
textDirection: isRTL ? TextDirection.rtl : TextDirection.ltr,
child: child!,
);
},
),
),
bloc/auth/auth_bloc.dart
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthBloc(this._authenticationRepository) : super(const AuthState()) {
on<AuthEvent>((event, emit) {
event.when(
authStateChangeEvent: (account) => _authStateChangeEvent(account, emit),
signOutEvent: _signOutEvent,
verifyAccount: _verifyAccount,
);
});
_userSubscription = _authenticationRepository.user.listen((account) {
add(AuthEvent.authStateChangeEvent(account));
});
}
late final StreamSubscription<Account> _userSubscription;
final AuthenticationRepository _authenticationRepository;
void _authStateChangeEvent(
Account account,
Emitter<AuthState> emit,
) {
...
}
void _signOutEvent() {
...
}
void _verifyAccount() {
...
}
#override
Future<void> close() {
_userSubscription.cancel();
return super.close();
}
}
Router.dart
class Routes {
Routes._();
static final _rootNavigatorKey = GlobalKey<NavigatorState>();
static GoRouter get router => _router;
static final GoRouter _router = GoRouter(
navigatorKey: _rootNavigatorKey,
refreshListenable: , // ! Listen to Auth Changes
routes: [..._directNavigations, ..._nestedNavigations],
redirect: _authGuard, //
);
static String? _authGuard(BuildContext context, GoRouterState state) {
final authState = context.watch<AuthBloc>().state;
print(authState.toString());
final isAuthenticated = authState.authStatus == AuthStatus.authenticated;
final isVerified =
authState.account != null && authState.account!.emailVerified;
/// ... Some logic for redirection
return null;
}
...
}
I'm getting this error when I'm navigating with context.go/context.push
"Tried to listen to a value exposed with provider, from outside of the widget tree.\n\nThis is
js_primitives.dart:30 likely caused by an event handler (like a button's onPressed) that called\nProvider.of without
js_primitives.dart:30 passing `listen: false`.\n\nTo fix, write:\nProvider.of<AuthBloc>(context, listen: false);\n\nIt is
js_primitives.dart:30 unsupported because may pointlessly rebuild the widget associated to the\nevent handler, when the
js_primitives.dart:30 widget tree doesn't care about the value.\n\nThe context used was:
js_primitives.dart:30 Navigator-[LabeledGlobalKey<NavigatorState>#31496](dependencies: [HeroControllerScope,
js_primitives.dart:30 UnmanagedRestorationScope, _FocusTraversalGroupMarker], state: NavigatorState#795d6(tickers:
js_primitives.dart:30 tracking 1 ticker))\n"
full crashlog: https://pastebin.com/3A3pdKtr
Note: I'm willing to replace my Auth BloC with a change notifier but I'm not sure if it's the right way to deal with this problem.
How can I access a variable in main.dart to other pages in flutter with Getx state management, Here I want to make the localMemberid in main.dart as Global to access from anywhere or pass it to other pages and is it the right way to use secure storage for storing the data
main.dart
void main() {
SecureStorage secureStorage = SecureStorage();
var localMemberid; // i would like to make this varial global or pass this value to other pages
runApp(
ScreenUtilInit(
builder: (BuildContext context, Widget? child) {
return GetMaterialApp(
title: "onyx",
initialRoute: AppPages.INITIAL,
getPages: AppPages.routes,
theme: ThemeData(primarySwatch: MaterialColor(0xFF0456E5, color)),
);
},
),
);
SecureStorage.readLocalSecureData('memberid')
.then((value) => localMemberid = value);
}
Login Controller
class LoginController extends GetxController {
final AuthenticationRepo _authRepe = AuthenticationRepo();
final SecureStorage secureStorage = SecureStorage();
String? localMemberid; // i would like to get the localMemberid from the main.dart
//TODO: Implement LoginController
#override
void onInit() {
super.onInit();
}
#override
void onReady() {
super.onReady();
}
#override
void onClose() {}
var userid;
var password;
onSinginButton() async {
var res = await _authRepe.login(username: userid, password: password);
if (res.status == ApiResponseStatus.completed) {
print(res.data);
await SecureStorage.writeLocalSecureData('memberid', res.data!.memberid);
localMemberid == null
? Get.toNamed(Routes.LOGIN)
: Get.toNamed(Routes.HOME);
} else {
Get.defaultDialog(title: res.message.toString());
}
}
}
Uplift your variable from the main function and make it Rx:
var localMemberid=Rxn<String>(); // i would like to make this varial global or pass this value to other pages
void main() {
SecureStorage secureStorage = SecureStorage();
.......
SecureStorage.readLocalSecureData('memberid')
.then((value) => localMemberid.value = value);
}
And then on your LoginController remove String? localMemberid; // and import main.dart:
localMemberid.value == null
? Get.toNamed(Routes.LOGIN)
: Get.toNamed(Routes.HOME);
When user logs into the app I need to set 'PWD' in the shared_preference variable. I need to get that value in splashcreen of my app so that when user opens the app again it need redirect to only password entering page. How can I do it in flutter.
onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
appdata.loginmode = prefs.setString('LOGIN_MODE', 'PWD');
Navigator.push(
context,
MaterialPageRoute(builder: (context) => BottomNavigation()),
);
print('Shared....');
print(prefs.getString('LOGIN_MODE'));
},
This what I am doing when user click login it will set to 'PWD', then I need to call the prefs in splashscree.
Short Answer
Not for splash screen but I am using the same logic for the onboard screen. I hope this answer will help. So, on your main.dart file, create a nullable int onBoardCount, outside of any class, you're gonna need this on your splash screen. Also, instantiate SharedPreferences in main and pass it with onboardcount to you MyApp();
int? onBoardCount;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
// Get onboard count from prefs, if it already exists,if not it will return null
onBoardCount = prefs.getInt('onBoardKey');
runApp(MyApp(prefs,onBoardCount));
}
Now, your MyApp file should be something like
class MyApp extends StatefulWidget {
late SharedPreferences prefs;
....
MyApp(this.prefs,this.onBoardCount, ...);
Now in your splash_screen.dart use the following logic.
void onSubmitDone(AppStateProvider stateProvider, BuildContext context) {
await prefs.setInt('onBoardKey', 0);
// Some route logic like route.push("/home");
}
Long Answer
I am using Go Router for routing and Provider for state management so, here's my app's code.
Main.dart
int? onBoardCount;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
onBoardCount = prefs.getInt('onBoardKey');
....
runApp(MyApp(prefs, onBoardCount));
}
I have a separate MyApp file to reduce congestion.
my_app.dart
class MyApp extends StatefulWidget {
late SharedPreferences prefs;
int? onBoardCount;
MyApp(this.prefs, this.onBoardCount,..... {Key? key})
: super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// The appstate provider is handling app level state
late AppStateProvider appStateProvider;
#override
void didChangeDependencies() {
super.didChangeDependencies();
appStateProvider = AppStateProvider(
widget.onBoardCount, widget.prefs,....);
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
....
ChangeNotifierProvider(
create: (context) => AppStateProvider(
widget.onBoardCount,
widget.prefs,...)),
Provider(
create: (context) => AppRouter(
appStateProvider: appStateProvider,
onBoardCount: widget.onBoardCount,
prefs: widget.prefs,
),
),
],
child: Builder(
builder: ((context) {
final GoRouter router = Provider.of<AppRouter>(context).router;
return MaterialApp.router(
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate);
}),
),
);
}
}
App State Provider File
Create a function to update onboard logic and notify listeners.
class AppStateProvider with ChangeNotifier {
AppStateProvider(this.onBoardCount, this.prefs,..);
int? onBoardCount;
late SharedPreferences prefs;
bool? _isOnboarded;
bool get isOnboard => _isOnboarded as bool;
void hasOnBoarded() async {
await prefs.setInt('onBoardKey', 0);
_isOnboarded = true;
notifyListeners();
}
}
On Router file
class AppRouter {
late AppStateProvider appStateProvider;
late SharedPreferences prefs;
int? onBoardCount;
AppRouter({
required this.appStateProvider,
required this.onBoardCount,
required this.prefs,
});
get router => _router;
late final _router = GoRouter(
refreshListenable: appStateProvider,
initialLocation: "/",
routes: [
...
],
redirect: (state) {
final String onboardLocation =
state.namedLocation("Your Route name");
bool isOnboarding = state.subloc == onboardLocation;
bool? toOnboard = prefs.containsKey('onBoardKey') ? false : true;
print("Is LoggedIn is $isLoggedIn");
if (toOnboard) {
return isOnboarding ? null : onboardLocation;
}
return null;
});
}
Since the router is listening to appStateProvider, it will change once you call hasOnBoarded() on your onboard screen.
OnBoardScreen
void onSubmitDone(AppStateProvider stateProvider, BuildContext context) {
stateProvider.hasOnBoarded();
GoRouter.of(context).go("/");
}
I hope this will help please leave comments. FYI, ... is some other codes that I feel it's not important for this topic.
This error keeps coming in my development
Debug Console
[ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: 'package:get_it/get_it_impl.dart': Failed assertion: line 312 pos 7: 'instanceFactory != null': Object/factory with type NavigationService is not registered inside GetIt.
E/flutter (27190): (Did you accidentally do GetIt sl=GetIt.instance(); instead of GetIt sl=GetIt.instance;
E/flutter (27190): Did you forget to register it?)
This is the navigator service file in which the navigation service as well as getit is initialised
navigation_service.dart
GetIt locator = GetIt.instance;
setupLocator() {
locator.registerLazySingleton(() => NavigationService());
}
class NavigationService {
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
Future<dynamic> navigateTo(routeName) {
return navigatorKey.currentState.pushReplacementNamed(routeName);
}
goBack() {
return navigatorKey.currentState.pop();
}
}
dynamic_link_service.dart
.
.
.
#some code
final NavigationService _navigationService = locator<NavigationService>();
if (deepLink != null) {
// print('_handleDeepLink | deepLink: $deepLink');
UserDataProvider().setReferralData(deepLink);
_navigationService.navigateTo(ReferralSignupPage.routeName);
}
.
.
.
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await setupLocator();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home:
.
.
.
routes: {
ReferralSignupPage.routeName: (ctx) => ReferralSignupPage(),
},
navigatorKey: locator<NavigationService>().navigatorKey,
);
}
This error keeps coming even after having tried all the possible solutions provided in other questions
Please help me out of this.
Regards
Just a guess here, but isn't the syntax:
final service = locator.get<NavigationService>();
Also possible you need to supply the generic parameter when registering the service.
add setupLocator();
void main() {
setupLocator();
runApp(MyApp());
}
When using shared_preferences on flutter in main.dart in order to change the initialRoute depending on if user have seen the first page or if user is logged in I am getting the boolean which is created throughout the app and added to shared_preferences, every time I start app, I get the initialRoute string correct when debugging, but I still end up getting on the first page, regardless the conditions.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:developer';
import './pages/registration.dart';
import './pages/login_page.dart';
import './pages/confirmation.dart';
import './pages/lang_page.dart';
import './pages/main_page.dart';
import './pages/user_data.dart';
import './provider/provider.dart';
void main() => runApp(CallInfoApp());
class CallInfoApp extends StatefulWidget {
#override
_CallInfoAppState createState() => _CallInfoAppState();
}
class _CallInfoAppState extends State<CallInfoApp> {
SharedPreferences prefs;
void getSPInstance() async {
prefs = await SharedPreferences.getInstance();
}
dynamic langChosen;
dynamic isLoggedIn;
String initialRoute;
void dataGetter() async {
await getSPInstance();
setState(() {
langChosen = prefs.getBool('langChosen');
// print(langChosen);
isLoggedIn = prefs.getBool('isLoggedIn');
});
}
void getRoute() async {
await dataGetter();
debugger();
if (langChosen == true && isLoggedIn != true) {
setState(() {
initialRoute = '/login_page';
});
} else if (isLoggedIn == true) {
initialRoute = '/main_page';
} else {
setState(() {
initialRoute = '/';
});
}
}
#override
void initState() {
super.initState();
debugger();
getRoute();
}
#override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return ChangeNotifierProvider<AppData>(
create: (context) => AppData(),
child: MaterialApp(
title: 'Call-INFO',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: initialRoute,
routes: {
'/': (context) => LanguagePage(),
'/registration_page': (context) => RegistrationPage(),
'/login_page': (context) => LoginPage(),
'/confirmation_page': (context) => ConfirmationPage(),
'/user_data_page': (context) => UserDataPage(),
'/main_page': (context) => MainPage(),
},
),
);
}
}
Since SharedPreference.getInstance() is an async function it will need some time until the instance is available. If you want to use it for initial route you have to make your main function async and preload it there before your MaterialApp is build.
SharedPreference prefs; //make global variable, not best practice
void main() async {
prefs = await SharedPreference.getInstance();
runApp(CallInfoApp());
}
And remove getSPInstance() from dataGetter
Also keep in midn that prefs.getBool('langChosen') will return null and not false if no entry is made into shared preference so use
langChosen = prefs.getBool('langChosen')??false;
isLoggedIn = prefs.getBool('isLoggedIn')??false;
While this solution will work it's not really good practice. I would recommend to have the initialRoute fixed to a splash screen and handle forwarding to the right page from there. A simple splash screen could look like that:
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: CircularProgressIndicator()));
}
#override
void initState() {
initSplash();
super.initState();
}
Future<void> initSplash() async {
final prefs = await SharedPreferences.getInstance();
final langChosen = prefs.getBool("lang_chosen") ?? false;
final isLoggedIn = prefs.getBool("logged_in") ?? false;
if (langChosen == true && isLoggedIn != true) {
Navigator.of(context).pushReplacementNamed('/login_page');
} else if (isLoggedIn == true) {
Navigator.of(context).pushReplacementNamed('/main_page');
} else {
Navigator.of(context).pushReplacementNamed('/');
}
}
}
Use initState to derive the data your logic is based on (i.e. fetching shared pref info). And use await keyword so that program will wait until the data is fetched from SharedPrefs. Adding the following code to class _CallInfoAppState should help
#override
void initState() {
super.initState();
dataGetter();
}
void dataGetter() async {
await getSPInstance();
setState(() {
langChosen = prefs.getBool('langChosen');
isLoggedIn = prefs.getBool('isLoggedIn');
});
}