Flutter problem with finding provider context - flutter

I have a problem with Flutter Provider pattern. After user is redirected to a new screen, the provider could not be found.
Following my previous question (Could not find the correct provider above this widget)
I wrote this code:
class NewRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
final title = 'Tap to select';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: NewRouteBody()
));
}
}
class NewRouteBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
var user = Provider.of<UserRepository>(context);
return ListView(...)
I did same thing but I get again the error which says that it could not find the correct provider above this widget (NewRouteBody).
Tried to fix it somehow, Googled the answer for a few hours but without success...
Any help is appreciated.
EDIT
This is UserRepository which contains pattern:
class UserRepository with ChangeNotifier {
User user;
Status _status = Status.Uninitialized;
Status get status => _status;
User get getUser => user;
...}
EDIT 2:
Code snippet with ChangeNotifier:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.red,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<UserRepository>(
builder: (context) => UserRepository.instance(),
child: Consumer<UserRepository>(
builder: (context, UserRepository userRepository, _) {
switch (userRepository.status) {
case Status.Uninitialized:
return Login();
case Status.Unauthenticated:
return Login();
case Status.Authenticating:
case Status.Authenticated:
if(userRepository.getUser.isPrefSet == 0){
return Selection();
}
return Dashboard();
}
},
),
);
}
}

The issue is:
Your ChangeNotifierProvider is located inside Home, but you are trying to access it outside Home.
Providers are scoped. Which means that if it's located inside a widget tree, only its descendants can access it. As such, in your code, only Home can read from the provider.
To fix that, move the provider above MaterialApp:
ChangeNotifierProvider<UserRepository> (
builder: (context) => UserRepository(),
child: MaterialApp(
home: Home(),
),
)

You first need to create the Provider and place in the tree above the usage.
for example, in your case:
Widget build(BuildContext context) {
final title = 'Tap to select';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Provider<UserRepository> (
builder: (context) => UserRepository(),
dispose: (context, val) => val.dispose(),
child: NewRouteBody())
));
}

When the application reports such an error, it can be from many reasons. In my case, I was trying to read data from a context that was not wrapped by the BlocProvider from its ancestor.
// In my Child Widget
Navigator.push(context, MaterialPageRoute(
builder: (_) => MultiBlocProvider(providers: [
BlocProvider.value(
value: SaveJobsCubit()),
BlocProvider.value(
value: context.read<OnlineCompaniesCubit>()),
BlocProvider.value(
value: context.read<ApplyJobsCubit>()),
],
child: AttractiveJobsScreen(),
)
// But in Parent Widget, I create MultiBlocProvider with case have access_token
AuthRepo.accessToken != null
? RepositoryProvider(
create: (context) => OnlineCompaniesRepo(),
child: MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => SaveJobsCubit(),
),
BlocProvider(
create: (context) => OnlineCompaniesCubit(context.read<OnlineCompaniesRepo>()),
),
BlocProvider(
lazy: false,
create: (context) => ApplyJobsCubit(),
),
],
child: current,
),
)
: RepositoryProvider(
create: (context) => OnlineCompaniesRepo(),
child: BlocProvider(
create: (context) => OnlineCompaniesCubit(context.read<OnlineCompaniesRepo>()),
child: current,
),
);
This causes an error in case there is no access_token, then the child screen will not have SaveJobsCubit and cause the above error.
Hope this helps someone.

Related

Could not find the correct Provider above the BlocListener Widget

I'm trying to use Bloc provider for user authentication in my flutter app. When I try to access the data i'm always getting this error even though I double checked all the files.
This is the error i'm getting:
Error: Could not find the correct Provider<StateStreamable<Object?>> above this
BlocListener<StateStreamable<Object?>, Object?> Widget
This happens because you used a `BuildContext` that does not include the provider
main.dart:
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => AuthBloc(LoginInitState(), AuthRepository()))
],
child: MaterialApp(
title: 'Flutter app',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity),
home: const LoginPage(),
),
);
}
}
parts from login.dart:
#override
void initState() {
authBloc = BlocProvider.of<AuthBloc>(context);
super.initState();
}
######################################################
return Scaffold(
backgroundColor: Colors.grey[300],
body: BlocListener(
listener: (context, state) {
if (state is UserLoginSuccessState) {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const HomeScreen()));
}
},
child: SafeArea...
I'm still new to flutter and struggling with the state management part, I'd be glad if anybody can help!
In your BlocListener you're missing the State and the Bloc
Here's what I mean
BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if (state is UserLoginSuccessState) {
Navigator.push(context,
MaterialPageRoute(builder: (context) => const HomeScreen()));
}
},
child: SafeArea...

Change the name route when user click widget - flutter web

I am declaring routes inside main.dart like this:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(isLogin: false),
'/home': (context) => Home(),
'/pageok': (context) => AnotherPage()
});
}
}
and I have custom side bar that I need to access in all pages that I have. Here is the way I define the side bar
#override
Widget build(BuildContext context) {
print(MediaQuery.of(context).size.width);
return Scaffold(
appBar: AppBarCustom(),
body: Stack(
children: <Widget>[
ValueListenableBuilder(
valueListenable: indexPage,
builder: (_, val, __) {
if (indexPage.value == 0)
return BerandaPelamar();
else if (indexPage.value == 1)
// Navigator.pushNamed(context, "/pageok"); ..... *****
return AnotherPage();
else
return Container(
color: Colors.red,
);
}),
//here is my custom side bar
ValueListenableBuilder(
valueListenable: indexPage,
builder: (_, val, __) {
return NavBarCustom(indexPage: indexPage);
})
],
));
}
So from ***** in my code I would like to return a Navigator that navigate user to a screen but I don't able to do that since I have to return a widget inside ValueListenableBuilder. Is there a way to do that or is there a way.. so that when user is in AnotherPage screen the route become http://localhost:59849/#/pageok

Using more than one provider in a flutter app

I have started using PROVIDER to manage state in my app. I followed tutorials and wrapped my Material app with ChangeNotifierProvider.
Here's the code :
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => ListsProvider(),
child: MaterialApp(
title: 'WordsApp',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: StartingPage.id,
routes: {
StartingPage.id: (context) => StartingPage(),
RegistrationScreen.id: (context) => RegistrationScreen(),
},
),
);
}
}
This provider called "ListsProvider" takes care of "providing" lists that need to be displayed on different screens.
I have now created a second provider which I called "user_data_provider" and I now need to add it to my app too. It will take care of providing user data to many different screens.
How can I do that ?
To achive this you can use Multiprovider as shown below
Add this to the top of your app. If you need these obj everywhere.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<user_data_provider>(
create: (_) => user_data_provider(),
),
ChangeNotifierProvider<ListsProvider>(
create: (_) => ListsProvider(),
),
],
child: Builder(
builder: (BuildContext context) {
return MaterialApp(
//YOur code goes here
);
},
),
);

Flutter authentification with streams

apparently I'm doing something fundamentally wrong here... After I login, I send new bool value (T) to isAuthorized stream. StreamBuilder reruns and ends up executing correct if-else branch, but for some reason Login Widget remains rendered on the screen?
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App',
theme: appTheme,
initialRoute: '/',
home: StreamBuilder<bool>(
stream: getIt.get<SessionBloc>().isAuthorized,
initialData: false,
builder: (context, snapshot) {
if (snapshot.data) {
return Home();
} else {
return Login();
}
},
),
routes: {
'/': (context) => Home(),
'/profile': (context) => Profile(),
'/login': (context) => Login(),
'/register': (context) => Register(),
},
),
);
}
}
Because I don't know what's your bloc doing, I created a simple stream that mimics the bloc. It is working fine with this code snippet. If the bloc gives you what you want and goes appropriate if-else block, then you should consider deleting '/': (context) => Home().
This was the exception that I had when I have home route:
If the home property is specified, the routes table cannot include an entry for "/", since it would be redundant.
class Authorization {
final _authorization = StreamController<bool>();
StreamSink<bool> get send => _authorization.sink;
Stream<bool> get isAuthorized => _authorization.stream;
get close => _authorization.close();
}
class MyApp extends StatelessWidget {
Authorization a = new Authorization();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App',
home: StreamBuilder<bool>(
stream: a.isAuthorized,
initialData: false,
builder: (context, snapshot) {
if (snapshot.data) {
return Home(auth: a);
} else {
return Login(auth: a);
}
},
),
routes: {
'/login': (context) => Login(auth: a),
},
);
}
}
class Home extends StatelessWidget {
final Authorization _auth;
Home({#required Authorization auth})
: assert(auth != null),
_auth = auth;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[Text("Home"), RaisedButton(
onPressed: () {
_auth.send.add(false);
},
)],
),
);
}
}
class Login extends StatelessWidget {
final Authorization _auth;
Login({#required Authorization auth})
: assert(auth != null),
_auth = auth;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[Text("Login"), RaisedButton(
onPressed: () {
_auth.send.add(true);
},
)],
),
);
}
}

Inherited widgets and navigator

I found some answers about this [here, here] but none of them completely answer my question.
I'm going to be using the package provider to describe my question because it greatly reduces the boilerplate code.
What I want to do is to inject a dependency when (and only when) a route is called. I can achieve that by doing something like this on onGenerateRoute:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Routes Demo',
initialRoute: '/',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => HomePage());
case '/firstPage':
return MaterialPageRoute(
builder: (_) => Provider(
builder: (_) => MyComplexClass(),
child: FirstPage(),
),
);
case '/secondPage':
return MaterialPageRoute(builder: (_) => SecondPage());
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('Route does not exists'),
),
),
);
}
});
}
}
class MyComplexClass {
String message = 'UUUH I am so complex';
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
RaisedButton(
child: Text('Go to first page'),
onPressed: () {
Navigator.pushNamed(context, '/firstPage');
}),
],
),
));
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final myComplexClass = Provider.of<MyComplexClass>(context);
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Center(
child: Text(myComplexClass.message),
),
RaisedButton(
child: Text('Go to second page'),
onPressed: () {
Navigator.pushNamed(context, '/secondPage');
},
)
],
),
),
);
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final myComplexClass = Provider.of<MyComplexClass>(context);
return Scaffold(
body: Center(
child: Text(myComplexClass.message),
),
);
}
}
This works fine for '/firstPage', but as soon as I push another route from inside 'firstPage' the context is lost and I loose access to MyComplexClass, since the navigator stays at the top of the tree together with MaterialApp the next route will loose the context where MyComplexClass was injected, I cannot manage to find a elegant solution to this.
This is the navigator stack we end up with:
As we can see SecondPage is not a child of Provider, hence the problem.
I don't want to inject all dependencies I have all at once on top of MainApp, I want to inject them as they're needed.
I considered creating new navigators each time I need another "fold", but that seems to become really messy very quickly.
How do I solve this issue?
In the following examples, both the route / and /login can access Provider.of<int>, but the route /other can't.
There are two solutions:
A StatefulWidget combined with a Provider.value that wraps each route.
Example:
class Foo extends StatefulWidget {
#override
_FooState createState() => _FooState();
}
class _FooState extends State<Foo> {
int myState = 42;
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/': (_) => Provider.value(value: myState, child: Home()),
'/login': (_) => Provider.value(value: myState, child: Login()),
'/other': (_) => Other(),
},
);
}
}
A private placeholder type that wraps MaterialApp combined with ProxyProvider:
Example:
class _Scope<T> {
_Scope(this.value);
final T value;
}
// ...
Widget build(BuildContext context) {
Provider(
builder: (_) => _Scope(42),
child: MaterialApp(
routes: {
'/': (_) => ProxyProvider<_Scope<int>, int>(
builder: (_, scope, __) => scope.value, child: Home()),
'/login': (_) => ProxyProvider<_Scope<int>, int>(
builder: (_, scope, __) => scope.value, child: Login()),
'/other': (_) => Other(),
},
),
);
}