Flutter - losing Provider state on hot reload - flutter

When I hot-reload my app, I lose state from my Provider classes. I'm aware that Provider properly preserves state so I know the issue comes from my code, but I cannot figure out where I'm going wrong. I have the following:
main.dart
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: GlobalData.fetchTheme(),
builder: (BuildContext context, AsyncSnapshot<String> themeSnapshot) {
if (themeSnapshot.hasData) {
GlobalData.theme = themeSnapshot.data;
return Phoenix(
child: MaterialApp(
title: 'MyApp',
home: Scaffold(body: Root()),
theme: ThemeData(
accentColor: Palette.green
),
)
);
}
return Loading();
}
);
}
}
root.dart
class Root extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder<FirebaseUser>(
stream: FireAuth().getInstance().onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
FirebaseUser user = snapshot.data;
if (user != null) {
GlobalData.setUid(user.uid);
}
return user == null ? Auth() : SafeArea(child: KeyboardDismisser(child: Core()));
} else {
return Loading();
}
},
);
}
}
core.dart
class Core extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
children: <Widget>[
FutureBuilder(
future: Users.getUser(GlobalData.uid),
builder: (BuildContext context, AsyncSnapshot<TuneUser> userSnapshot) {
if (userSnapshot.hasData) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserProvider(userSnapshot.data)),
ChangeNotifierProvider(create: (context) => SearchProvider()),
ChangeNotifierProvider(create: (context) => DetailsProvider()),
],
child: Expanded(child: BottomBarNavigation())
);
}
return Expanded(child: Loading());
}
),
BottomBar()
]
);
}
}
I assume at some point I'm rebuilding or losing state when I shouldn't, I've tried a lot of things with the help of similar questions already present, but I can't seem to fix it.
Any ideas?
Would really appreciate the help.
Cheers,
Andy

wrap you MultiProvider in the main.dart
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UserProvider(userSnapshot.data)),
ChangeNotifierProvider(create: (context) => SearchProvider()),
ChangeNotifierProvider(create: (context) => DetailsProvider()),
],
child: child: MaterialApp(
title: 'MyApp',
home: Scaffold(body: Root()),
theme: ThemeData(
accentColor: Palette.green
),
),
),

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...

Could not find the correct Provider<TokenBloc> above this App Widget

I using BLoC. How to create it correctly, what would not arise due to the lack of widgets down the widget tree. Now I usually like this:
Widget build(BuildContext context) {
return MaterialApp(
// debugShowCheckedModeBanner: false,
theme: Styles.appTheme,
home: BlocProvider<TokenBloc>(
create: (context) => di.sl<TokenBloc>(),
child: _childTokenBloc,
),
);
}

Widget get _childTokenBloc {
return BlocBuilder<TokenBloc, TokenState>(builder: (context, state) {
if (state is TokenInitialState) {
context.read<TokenBloc>().add(TokenCheckEvent());
return const LogoImage();
}
if (state is TokenCheckState) {
return const LogoImage();
}
if (state is TokenOkState) {
return MainPageWidget();
}
if (state is TokenNoAuthorizationState) {
return const AuthorizationPageWidget();
}
return const LogoImage();
}
);
}
In AuthorizationPageWidget I do:
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ConfirmAuthorizationPage()),
);
And from ConfirmAuthorizationPage I try to turn to TokenBloc:
context.read<TokenBloc>().add(TokenAddEvent());
but I get Error: Could not find the correct Provider above this App Widget
I thought that TokenBloc would be found in the widget tree, but is it not? And how to fix this problem? Need to use MultiBlocProvider in the build method of the ConfirmAuthorizationPage widget? It will be re-initialized, and the previous one will not be used.
Update 1:
Code AuthorizationPageWidget:
class AuthorizationPageWidget extends StatefulWidget {
const AuthorizationPageWidget({Key? key}) : super(key: key);
#override
_AuthorizationPageWidgetState createState() =>
_AuthorizationPageWidgetState();
}
class _AuthorizationPageWidgetState extends State<AuthorizationPageWidget> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider<AuthorizationBloc>(
create: (context) => sl<AuthorizationBloc>(),
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_title,
_description,
Expanded(child: Align(alignment: FractionalOffset.bottomCenter, child: _bottomButton))
],
),
),
),
);
}
//......
void pushConfirmPage(String number) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ConfirmAuthorizationPage(number: number,)),
);
}
}
If you want to provide your Bloc in all your application, you have to write it in your MaterialApp like this, not in the body ;
return
BlocProvider<TokenBloc>( // like this
create: (context) => TokenBloc(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: _TokenHome(),
),
),
);
class _TokenHome extends StatelessWidget { // use a class instead of function
const _TokenHome({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocBuilder<TokenBloc, TokenState>(
builder: (context, state) {
if (state is TokenInitialState) {
context.read<TokenBloc>().add(TokenCheckEvent());
return const LogoImage();
}
if (state is TokenCheckState) {
return const LogoImage();
}
if (state is TokenOkState) {
return MainPageWidget();
}
if (state is TokenNoAuthorizationState) {
return const AuthorizationPageWidget();
}
return Container(
width: 50,
height: 50,
color: Colors.red,
); // use this if there is not a state
}
);
}
}
If for some reason it doesn't show anything anymore, then it's because some of your classes like AuthorizationPageWidget or LogoImage are wrong, check that.
-------- EDIT
Using BlocProvider on each page can be useful, but keep in mind that for example AuthorizationBloc will only work for its children, if you call it on another side of the screen it will not work, so it is highly recommended to use a MultiBlocProvider in MaterialApp to avoid future problems;
return MultiBlocProvider( // like this
providers: [
BlocProvider<TokenBloc>(
create: (context) => TokenBloc(),
),
BlocProvider<AuthorizationBloc>(
create: (context) => AuthorizationBloc(),
),
],
child: BlocBuilder<LanguageCubit, Locale?>(
builder: (context, lang) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
);
},
),
);
So all the other BlocProvider that you use to create, delete them, you do not need them, now if you use a BlocBuilder, BlocListeners of any Bloc, you would not have any inconvenience.

Flutter 'No Overlay widget exists above EditableText' error when using Provider

Hey I've got a MultiProvider setup, and it's now throwing this error when tapping on a TextField whereas it didn't before I implemented the multi-provider:
No Overlay widget exists above EditableText
The error text does not give any helpful indication of what in the code is causing the issue, here is the code:
import 'package:***_mobile/Providers/user_auth_provider.dart';
import 'package:***_mobile/screens/browsing_page.dart';
import 'package:***_mobile/screens/film_details_page.dart';
import 'package:***_mobile/screens/login_screen.dart';
import 'package:***_mobile/screens/venue_details_page.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(***());
}
class *** extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => UserRepository.instance(),
child: Consumer(
builder: (context, UserRepository user, _) {
return MaterialApp(
title: 'Flutter Demo',
routes: {
'/browsing': (ctx) => BrowsingPage(),
'/venueDetails': (ctx) => VenueDetailPage(),
'/filmDetails': (ctx) => FilmDetailPage(),
'/login': (ctx) => LoginPage()
},
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
builder: (ctx, _) {
switch (user.status) {
case Status.Uninitialized:
return LoginPage();
case Status.Unauthenticated:
return LoginPage();
case Status.Authenticating:
return LoginPage();
case Status.Authenticated:
return BrowsingPage();
default:
return LoginPage();
}
},
);
},
),
);
}
}
Excuse for my language, i hope you will understand, what i was wrote.
You need wrap every widget, which you return from builder in Navigator
Navigator(
onGenerateRoute: (_) => MaterialPageRoute(
builder: (ctx) => Scaffold(
body: Stack(
children: [
Positioned.fill(
child: buildBody(
context.watch<AuthScopeViewModel>().screenType))
],
),
),
),
);

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);
},
)],
),
);
}
}

Flutter problem with finding provider context

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.