go_router and flutter_bloc: Unhandled Exception: No GoRouter found in context - flutter

I have wrapped the MaterialApp with a BlocProvider / BlocListener
I get an error
"Unhandled Exception: 'package:go_router/src/router.dart': Failed assertion: line 280 pos 12: 'inherited != null': No GoRouter found in context" from the Listener callback
Widget build(BuildContext context) {
return BlocProvider<AuthenticationBloc>(
create: (context) => AuthenticationBloc()..add(AppStarted()),
child: BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state is AuthenticationUnauthenticated) {
context.goNamed(LoginPage.routeName);
}
if (state is AuthenticationAuthenticated) {
context.goNamed(NavigationBarContainer.routeName);
}
},
child: MaterialApp.router(
title: 'Flutter Demo',
routeInformationProvider: _router.routeInformationProvider,
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
theme: ThemeData(
primarySwatch: Colors.blue,
)),
),
);
}

This is happening because you are using context of go_router before it is initialized !!
Widget build(BuildContext context) {
return BlocProvider<AuthenticationBloc>(
create: (context) => AuthenticationBloc()..add(AppStarted()),
child: BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state is AuthenticationUnauthenticated) {
context.goNamed(LoginPage.routeName); // 👈 Go router is not initilaized yet
}
if (state is AuthenticationAuthenticated) {
context.goNamed(NavigationBarContainer.routeName); // 👈 Go router is not initilaized yet
}
},
child: MaterialApp.router(
routeInformationProvider: _router.routeInformationProvider,
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate, // 👈 Your router is initialized here
),
);
}
Make changes to :
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AuthenticationBloc>(
create: (context) => AuthenticationBloc()..add(AppStarted()), 👈 Specify only the BlocProvider here
),
],
child: MaterialApp(
theme: customTheme(context),
debugShowCheckedModeBanner: false,
routeInformationProvider: _router.routeInformationProvider,
routeInformationParser: _router.routeInformationParser,
routerConfig: router,
}
And then try to navigate inside the routerlike:
final GoRouter router = GoRouter(routes: [
GoRoute(
path: "/",
builder: (context, state) {
return BlocBuilder<AuthCubit, AuthState>(
buildWhen: (oldState, newState) {
return oldState is AuthInitialState;
},
builder: (context, state) {
if (state is AuthenticationUnauthenticated) {
// return const LoginPage(); // alternative way
context.goNamed(LoginPage.routeName); 👈 Use conditional routing using context here
} else if (state is NavigationBarContainer.routeName) {
// return SignIn(); // alternative way
context.goNamed(NavigationBarContainer.routeName); 👈 Use conditional routing using context here
} else {
return const Scaffold();
}
},
);
}),

You are attempting to navigate with context from higher up in the widget tree than go_router gets inserted.
I don't know where your GoRouter() routerConfig is, and where it is called from when you use a RouterDelegate, (maybe you don't need to use a delegate?) but you need to call your GoRouter configuration directly, and navigate from that.
So you need to change:
context.goNamed(LoginPage.routeName)
to
routerConfig.goNamed(LoginPage.routeName)
For me, you can see I pass the routerConfig to MaterialApp.router, and I also navigate directly from that, with routerConfig.go(HOME), from above the MaterialApp:
ref.watch(authStatusServiceProvider).whenData((authStatus) {
switch (authStatus) {
case AuthenticationStatusEnum.authenticated:
routerConfig.go(HOME);
break;
case AuthenticationStatusEnum.unauthenticated:
routerConfig.go(LOGGED_OUT_HOME);
break;
default:
routerConfig.go(LOGGED_OUT_HOME);
break;
}
});
return MaterialApp.router(
theme: lightTheme,
debugShowCheckedModeBanner: false,
darkTheme: darkTheme,
routerConfig: routerConfig,
);
}
All credit goes to darshankawar on Github.

Related

multi provider not changing state

I am developing a chat app and all was fine when i was using only a stream provider which takes user id stream from firebase, but as i want real time changes when i add a chat, so i added multi provider, and gives it stream provider and change notfier provider, now both are not working, i have to hot restart the app for changes.
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// authentication provider
StreamProvider<User?>(
create: (context) => AuthController().userStream(),
initialData: null,
),
//states provider
ChangeNotifierProvider(create: (context) => Cloud()),
],
builder: (context, _) => MaterialApp(
theme: ThemeData(
primaryColor: Colors.deepPurpleAccent,
textTheme: TextTheme(button: TextStyle(color: Colors.white)),
primarySwatch: Colors.deepPurple),
home: Scaffold(
body: Wrapper(),
),
));
}
You can Simply Use SteamBuilder & Firestore :
#override Widget build(BuildContext context) {
var streamBuilder = StreamBuilder<List<Message>>(
stream: getData(),
builder: (BuildContext context, AsyncSnapshot<List<Message>> messagesSnapshot) {
if (messagesSnapshot.hasError)
return new Text('Error: ${messagesSnapshot.error}');
switch (messagesSnapshot.connectionState) {
case ConnectionState.waiting: return new Text("Loading...");
default:
return new ListView(
children: messagesSnapshot.data.map((Message msg) {
return new ListTile(
title: new Text(msg.message),
subtitle: new Text(DateTime.fromMillisecondsSinceEpoch(msg.timestamp).toString()
+"\n"+(msg.user ?? msg.uid)),
);
}).toList()
);
}
}
);
return streamBuilder; }

MultiRepositoryProvider doesn't instantiate Bloc

I recently started developing an app in Flutter, so I'm fairly new to the area. So I've been looking into using Blocs. However when I Instantiate my Bloc and my services everything works fine. That is, until I use MultiRepositoryProvider. I have 2 code snippets. The first one:
return RepositoryProvider<AuthenticationService>(
create: (context) {
return FakeAuthenticationService();
},
// Injects the Authentication BLoC
child: BlocProvider<AuthenticationBloc>(
create: (context) {
final authService = RepositoryProvider.of<AuthenticationService>(context);
return AuthenticationBloc(authService)..add(AppLoaded());
},
child: MaterialApp(
title: 'Authentication Demo',
theme: appTheme(),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is AuthenticationAuthenticated) {
// show home page
return HomePage(
user: state.user,
);
}
// otherwise show login page
return StartupPage();
},
),
)
),
);
This code works fine, but the second snippet which is exactly the same, except it utilized MultiRepositoryProvider doesn't work. Second code:
return MultiRepositoryProvider(
providers: [
RepositoryProvider<AuthenticationService>(
create: (context) => FakeAuthenticationService(),
child: BlocProvider<AuthenticationBloc>(
create: (context) {
final authService = RepositoryProvider.of<AuthenticationService>(context);
return AuthenticationBloc(authService)..add(AppLoaded());
},
),
)
],
child: MaterialApp(
title: 'Authentication Demo',
theme: appTheme(),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is AuthenticationAuthenticated) {
// show home page
return HomePage(
user: state.user,
);
}
// otherwise show login page
return StartupPage();
},
),
),
);
Now this second code gives me the error BlocProvider.of() called with a context that does not contain a Cubit of type AuthenticationBloc.
Does anyone know why this second code doesn't work?
I'm working on the same thing and I got an error but now resolved
return MultiRepositoryProvider(
providers: [
RepositoryProvider<TranslationRepository>(
create: (context) => TranslationRepository(),
),
RepositoryProvider<WeatherRepository>(
create: (context) => WeatherRepository(),
),
],
child: MultiBlocProvider(
providers: [
BlocProvider<WeatherBloc>(
create: (context) =>
WeatherBloc(context.read<WeatherRepository>()),
),
BlocProvider<ConnectivityBloc>(
create: (context) => ConnectivityBloc(),
),
BlocProvider<TranslationBloc>(
create: (context) =>
TranslationBloc(context.read<TranslationRepository>()),
),
],
child: MaterialApp(
title: 'Material App',
onGenerateRoute: router.generateRoute,
initialRoute: '/',
)));
First, in my create function I overrided the context with "_" but I got the same error.
Now with this snippet it works perfectly, just put the same context name as my providers before

Flutter: firebase authorization flow won't work with routes

I'm trying to make sure that app-users that aren't authorised (signed in), are directed to the sign in page. This is what my main.dart looks like:
class App extends StatelessWidget {
Widget build(BuildContext context) {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.active) {
// some loading widget
return MaterialApp(home: Scaffold(),);
}
FirebaseUser user = snapshot.data;
if (user == null) {
return MaterialApp(home: SignIn(),);
}
// this is the main app
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/new_game': (context) => NewGame(),
'/join_session': (context) => JoinSession(),
'/my_rankings': (context) => MyRankings(),
'/settings': (context) => Settings(),
},
);
}
);
}
}
When I run the app (on chrome using the web function) I start of at the sign in page (as expected) which just contains an anonymous sign in button. When I sign in it gives an error, stating that the initial-route-builder (route '/' with HomeScreen() as builder) returns null. When I swap my the main app for a simple
return MaterialApp(home: Scaffold(body: Text('This Works')));
it does seem to work. When using a simple MaterialApp() that does the same but using routes, it gives the error again, so the problem seems to be the routing. What's going on?
i don't think it will work like that. the main.dart should be used only as the app entry point.
class App extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => SplashScreen(),
'/home': (context) => HomeScreen(),
'/new_game': (context) => NewGame(),
'/join_session': (context) => JoinSession(),
'/my_rankings': (context) => MyRankings(),
'/settings': (context) => Settings(),
},
);
}
}
class SplashScreen extends StatelessWidget {
void isLogged StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.active) {
// some loading widget
Navigator.pushReplacementNamed(context, '/home');
}
FirebaseUser user = snapshot.data;
if (user == null) {
Navigator.pushReplacementNamed(context, '/SignIn');
}
// this is the main app
);
initiatState(){
isLogged();
}
Widget build(BuildContext context){
}
}
You could use this method to check if a user is logged auth.currentUser() it returns null if the user is not signed
FirebaseAuth auth = FirebaseAuth.instance;
await auth.currentUser() == null ? false : true;
Just do this!
import 'package:base_app/screens/auth/login/index.dart';
import 'package:base_app/screens/boarding/index.dart';
import 'package:base_app/screens/main/home/index.dart';
import 'package:base_app/screens/main/profile/index.dart';
import 'package:base_app/screens/splash/index.dart';
import 'package:base_app/style/palette.dart';
import 'package:base_app/style/theme.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:sizer/sizer.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Sizer(
builder: (context, orientation, deviceType) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
fontFamily: 'DancingScript',
textTheme: getTextTheme(context),
primarySwatch: colorRed as MaterialColor,
),
home: LandingFlow(),
routes: {
ProfileScreen.id: (context) => ProfileScreen(),
},
);
},
);
}
}
class LandingFlow extends StatefulWidget {
#override
_LandingFlowState createState() => _LandingFlowState();
}
class _LandingFlowState extends State<LandingFlow> {
bool isSplashOver = false;
bool hasBoardingScreensShown = true;
#override
Widget build(BuildContext context) {
if (isSplashOver) {
if (hasBoardingScreensShown) {
return StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasData) {
debugPrint('HomeScreen');
return HomeScreen();
} else {
debugPrint('LoginScreen');
return LoginScreen();
}
},
);
}
return BoardingScreen();
}
return SplashScreen(
onFinished: () => setState(() {
isSplashOver = true;
}),
);
}
}
And for your Splash Screen:
import 'package:flutter/material.dart';
class SplashScreen extends StatelessWidget {
static const id = 'SplashScreen';
final Function onFinished;
const SplashScreen({this.onFinished});
#override
Widget build(BuildContext context) {
Future.delayed(
const Duration(seconds: 3),
() => onFinished(),
);
return const Scaffold(
body: Center(
child: Text('Splash Screen'),
),
);
}
}

Multiple ScopedModels in flutter

I followed a tutorial from a flutter app with login system using a scope_model. Then, I added a new scope_model called Group to use in a new "route" called opportunities.
But in my new route I can't call the scope_model Group and I allways see the same error:
Error: Could not find the correct ScopedModel.
I think that my mistake is in main.dart. I don't know how to "invoque" my new scope_model.
Here is my code.
file opportuinity.dart
import 'package:scoped_model/scoped_model.dart';
import 'package:business_maker/data/models/group_api.dart';
(...)
#override
Widget build(BuildContext context) {
final _group = ScopedModel.of<GroupModel>(context, rebuildOnChange: true);
file main.dart
#override
Widget build(BuildContext context) {
return ScopedModel<ThemeModel>(
model: _model,
child: new ScopedModelDescendant<ThemeModel>(
builder: (context, child, theme) => ScopedModel<AuthModel>(
model: _auth,
child: MaterialApp(
theme: theme.theme,
home: new ScopedModelDescendant<AuthModel>(
builder: (context, child, model) {
if (model?.user != null) return Home();
return LoginPage();
}),
routes: <String, WidgetBuilder>{
"/login": (BuildContext context) => LoginPage(),
"/menu": (BuildContext context) => Home(),
"/home": (BuildContext context) => Home(),
"/settings": (BuildContext context) => SettingsPage(),
"/opportunities": (BuildContext context) => OpportunityPage()
},
),
),
));
}
thank you
If you want to use models in different routes then you need to place the model above the Navigator which is usually created in a WidgetsApp/MaterialApp/CupertinoApp
In your code I do not see a ScopedModel<Group> that's placed above the navigator. Or anywhere, actually. You need to add the group model above the navigator (something the materialapp creates for you).
Widget build(BuildContext context) {
return ScopedModel<ThemeModel>(
model: _model,
child: ScopedModel<Group>(
model: _yourGroupModel,
child: new ScopedModelDescendant<ThemeModel>(
builder: (context, child, theme) => ScopedModel<AuthModel>(
model: _auth,
child: MaterialApp(
theme: theme.theme,
home: new ScopedModelDescendant<AuthModel>(
builder: (context, child, model) {
if (model?.user != null) return Home();
return LoginPage();
}),
routes: <String, WidgetBuilder>{
"/login": (BuildContext context) => LoginPage(),
"/menu": (BuildContext context) => Home(),
"/home": (BuildContext context) => Home(),
"/settings": (BuildContext context) => SettingsPage(),
"/opportunities": (BuildContext context) => OpportunityPage()
},
),
),
)
)
);
}

Flutter: Dynamic Initial Route

Dears,
I am using provider dart package which allows listeners to get notified on changes to models per se.
I am able to detect the change inside my main app root tree, and also able to change the string value of initial route however my screen is not updating. Kindly see below the code snippet and the comments lines:
void main() => runApp(_MyAppMain());
class _MyAppMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<UserProvider>.value(
value: UserProvider(),
),
ChangeNotifierProvider<PhoneProvider>.value(
value: PhoneProvider(),
)
],
child: Consumer<UserProvider>(
builder: (BuildContext context, userProvider, _) {
return FutureBuilder(
future: userProvider.getUser(),
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final User user = snapshot.data;
String initialScreen = LoginScreen.path;
// (1) I am able to get into the condition
if (user.hasActiveLogin()) {
initialScreen = HomeOneScreen.path;
}
return MaterialApp(
title: 'MyApp',
theme: ThemeData(
primarySwatch: Colors.green,
accentColor: Colors.blueGrey,
),
initialRoute: initialScreen,
// (2) here the screen is not changing...?
routes: {
'/': (context) => null,
LoginScreen.path: (context) => LoginScreen(),
RegisterScreen.path: (context) => RegisterScreen(),
HomeOneScreen.path: (context) => HomeOneScreen(),
HomeTwoScreen.path: (context) => HomeTwoScreen(),
RegisterPhoneScreen.path: (context) => RegisterPhoneScreen(),
VerifyPhoneScreen.path: (context) => VerifyPhoneScreen(),
},
);
},
);
},
),
);
}
}
Kindly Note the Below:
These are are paths static const strings
LoginScreen.path = "login"
RegisterScreen.path = "/register-screen"
HomeOneScreen.path = "home-one-screen"
HomeTwoScreen.path = "home-two-screen"
RegisterPhoneScreen.path = "/register-phone-screen"
VerifyPhoneScreen.path = "/verify-phone-screen"
What I am missing for dynamic initialRoute to work?
Many Thanks
According to this issue described on github issues it is not permissible to have initial route changes. At least this is what I understood. However what I did is that I replaced the initialRoute attribute with home attr. Thus this change mandates that initialScreen becomes a widget var.
The changes is shown below:
void main() => runApp(_MyAppMain());
class _MyAppMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<UserProvider>.value(
value: UserProvider(),
),
ChangeNotifierProvider<PhoneProvider>.value(
value: PhoneProvider(),
)
],
child: Consumer<UserProvider>(
builder: (BuildContext context, userProvider, _) {
return FutureBuilder(
future: userProvider.getUser(),
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final User user = snapshot.data;
// (1) This becomes a widget
Widget initialScreen = LoginScreen();
if (user.hasActiveLogin()) {
initialScreen = HomeOneScreen();
}
return MaterialApp(
title: 'MyApp',
theme: ThemeData(
primarySwatch: Colors.green,
accentColor: Colors.blueGrey,
),
home: initialScreen,
// (2) here the initial route becomes home attr.
routes: {
'/': (context) => null,
LoginScreen.path: (context) => LoginScreen(),
RegisterScreen.path: (context) => RegisterScreen(),
HomeOneScreen.path: (context) => HomeOneScreen(),
HomeTwoScreen.path: (context) => HomeTwoScreen(),
RegisterPhoneScreen.path: (context) => RegisterPhoneScreen(),
VerifyPhoneScreen.path: (context) => VerifyPhoneScreen(),
},
);
},
);
},
),
);
}
}
Also note on my RegistrationScreen on success api response I did Navigator.of(context).pop()
Thanks