I have been working on an app, here the basic structure looks like.
Having a MultiblocProvider. With two routes.
Route generateRoute(RouteSettings routeSettings) {
switch (routeSettings.name) {
case BASE_ROUTE:
return MaterialPageRoute(
builder: (_) => BlocProvider(
create: (context) => SignupCubit(),
child: SignUp(),
),
);
case OTP_VERIFY:
return MaterialPageRoute(
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => VerifyCubit(),
),
BlocProvider(
create: (context) => SignupCubit(),
),
],
child: Verify(),
),
);
default:
return MaterialPageRoute(builder: (_) => Broken());
}
}
In OTP_Verify route I am giving access to two Cubit, VerifyCubit() and SignupCubit().
Now, what i am doing is,
There is two Screen, one is SignUp and the other is Verify. In SignUp Screen, if the state is SignUpSuccess, I am navigating to verify OTP screen.
class SignUp extends StatelessWidget {
const SignUp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
double deviceHeight = MediaQuery.of(context).size.height;
return Scaffold(
body: BlocListener<SignupCubit, SignupState>(
listener: (context, state) {
if (state is SignUpError) {
showToast("Please try again");
} else if (state is SignupSuccess) {
print(state.email);
Navigator.pushNamed(context, OTP_VERIFY); <--- Here
} else if (state is EmailValidationError) {
showToast("Not valid email");
}
},
child: SafeArea(
bottom: false,
child: CustomScrollView(
slivers: [
.... rest of code....
In VerifyOTP screen, i am trying to read state of current SignUpCubit
....other code....
ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(45),
primary: Theme.of(context).primaryColor),
onPressed: () {
final signUpState = BlocProvider.of<SignupCubit>(context).state; <--- Here
if (signUpState is SignupSuccess) {
print(signUpState.email);
}
BlocProvider.of<VerifyCubit>(context).setOtp(otp);
},
child: const Text('Verify'),
),
.....other code.....
This is my SignUpState
part of 'signup_cubit.dart';
#immutable
abstract class SignupState {}
class SignupIntial extends SignupState {}
class SignUpError extends SignupState {}
class SignupSuccess extends SignupState {
final String email;
SignupSuccess({required this.email});
}
class EmailValidationError extends SignupState {}
Now what I am assuming is I already emitted SignupSuccess in first page and I could read it in second page if I have provided that state by MultiBlocProvider.
But its not happening. Insted I am getting SignUpIntial.
Can someone please help, what i could be doing wrong, or is my method even valid ?
that's because you provide a new instance of the SignupCubit while routing to Verify Screen. thus BlocProvider.of<SignupCubit>(context).state will return the state of the cubit above it which is still in the initial state.
I don't know why you need to check the state of the SignupCubit in the Verify Since you only navigate to it when it's SignupSuccess but anyway, a quick workaround is that you declare and initialize an instance of SignupCubit and use it in the provider around the SignUp and Verify Screens.
Related
I want to use MultiBlocProvider as shown below.
How to use MultiBlocProvider in specific level in flutter tree widget ?
In other words, when we use MultiBlocProvideron top of MaterialApp, there is no problem. But according to the code below, this item gets an error.
example:
void main() {
runApp(MaterialApp(
onGenerateRoute: (settings) {
switch (settings.name) {
case "/":
return MaterialPageRoute(
builder: (_) => MultiBlocProvider(providers: [
BlocProvider(
create: (_) => CounterBloc(),
)
], child: const GroupA()),
settings: settings);
case "/ScopeA":
return MaterialPageRoute(
builder: (_) => const ScopeA(), settings: settings);
default:
return MaterialPageRoute(
builder: (_) => const Text("ERROR"), settings: settings);
}
},
));
}
class GroupA extends StatelessWidget {
const GroupA({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Group A:')),
body: Center(
child: MaterialButton(
onPressed: () => Navigator.pushNamed(context, '/ScopeA'),
child: const Text("Go To Scope A")),
),
);
}
}
class ScopeA extends StatelessWidget {
const ScopeA({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Scope A:')),
body: Center(
child: BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Text(
'$count',
style: Theme.of(context).textTheme.displayLarge,
);
},
),
),
);
}
}
abstract class CounterEvent {}
class CounterIncrementPressed extends CounterEvent {}
class CounterDecrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
on<CounterDecrementPressed>((event, emit) => emit(state - 1));
}
}
i using below version :
flutter_bloc : 8.1.2
bloc : 8.1.1
error:
Error: Cannot hit test a render box that has never been laid out.
You're misleading the concept behind passing blocs through sub-tree and through Navigator widgets.
Basically, the MultiBlocProvideror BlocProvider make a bloc accessible in all the subtrees, so the bloc will be available only in GroupA's subtree of widgets, by calling Navigator.pushNamed(), what does happen is that another separated sub-tree will be put in the Navigator child, so at this point the GroupA and ScopeA will not be in the same widget-tree, even if it seems to when you see a page route is set on top of other's on the Flutter UI, I can represent it like this:
-> MultiBlocProvider -> GroupA
Navigator => |
-> ScopeA
and as you conclude, the bloc that is available inside the GroupA will not be available in ScopeA, until you pass it in somehow, like using BlocProvider.value():
case "/ScopeA":
return MaterialPageRoute(
builder: (context) {
return BlocProvider.value(
value: context.read<CounterBloc>(),
child: const ScopeA(),
);
},
settings: settings,
);
or by making the bloc accessible through the whole app, so you will have a Flutter tree like this:
-> GroupA
MultiBlocProvider -> Navigator => |
-> ScopeA
I'm new in flutter and trying to understand flutter state management concept using provider. This the image scenario what I'm trying to do
I have created a file called auth_provider.dart file under the folder called Providers
class AuthProvider with ChangeNotifier{
bool isLogin = false;
Future createUser() async
{
isLogin = true;
notifyListeners();
}
Future login() async
{
isLogin = true;
notifyListeners();
}
void logout()
{
isLogin = false;
notifyListeners();
}
}
This the Signup button that I have created in the login page
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SignupPage()
),
);
},
child: const Text(
'Signup Button',
),
)
This is the signUp button in signup screen
child: ElevatedButton(
onPressed: () => signUpSubmit(),
child: const Text(
'Sign Up',
),
),
I have written a signUpSubmit future like below
Future<void> signUpSubmit() async {
Provider.of<AuthProvider>(context, listen: false).createUser();
}
I have used AuthProvider consumer in main.dart page
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => AuthProvider(),
),
],
child: Consumer<AuthProvider>(
builder: (ctx,auth,child){
print(auth.isLogin);
return MaterialApp(
home: auth.isLogin ? const HomeScreen():const LoginPage(),
routes: {
HomeScreen.routeName: (ctx) => const HomeScreen(),
SignupPage.routeName: (ctx) => const SignupPage(),
LoginPage.routeName: (ctx) => const LoginPage(),
},
);
}
),
);
}
}
After click on signup button I'm getting true in main page , which I have given a print under Consumer builder in main.dart page. So according to MaterialApp widget home condition page should redirect to HomeScreen but it's not moving. Why it's not moving ? What is the main cause and what it the best way to solve this problem ?
Note : If I try it from login screen redirection is working fine. But according to my image flow (Login -> signup) it's not working.
here is the code you are looking for, but bear in mind with the implementation you have right now, if the user opens the app again, it will redirect them to the signin page. because the boolean value will disappear once the user closes the app.
change your main.dart file like the following..
main function
void main() {
// you just need to add the multiprovider and the change notifier provider class
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthProvider()),
],
child: const MyApp(),
),
);
}
here is the MyApp class as i understand it.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(builder: (ctx, auth, child) {
print(auth.isLogin);
return MaterialApp(
home: auth.isLogin ? MyHomePage() : LoginPage(),
routes: {
MyHomePage.routeName: (ctx) => MyHomePage(),
LoginPage.routeName: (ctx) => LoginPage(),
//NavScreen.routeName: (ctx) => const NavScreen(),
},
);
});
}
}
Change the signup button in the register page to the following.
ElevatedButton(
onPressed: () {
signUpSubmit(context);
Navigator.of(context).pushNamed(HomeScreen.routeName);
},
and the signupsubmit function like this..
signUpSubmit(BuildContext context) {
Provider.of<AuthProvider>(context, listen: false).createUser();
}
The main cause of your problem is that you are pushing a new route (screen) from login page and the best way to solve problem is to pop that route (screen) from sigupPage.
On click of Signup button from login page you are pushing a new route, so in order to redirect to HomeScreen from SignupPage first you need to pop that route so that you can see the updated changes.
Future<void> signUpSubmit() async {
Navigator.of(context).pop();
Provider.of<AuthProvider>(context, listen: false).createUser();
}
https://docs.flutter.dev/cookbook/navigation/navigation-basics
i want to transfer my bloc provider to other page but how can i do that ?
I have a sign in page. If user can log in the app (with email and password) he goes to homePage. The program gives error, because sign in page has signInCubit, but homePage has not. How can i transfer this bloc builder ? I tried blocprovider.value but it can't.
it gives this error: Error: Could not find the correct Provider above this BlocListener<SignInCubit, SignInState> Widget
my sign in page:
class SignInPage extends StatelessWidget {
static const String id = 'sign_in_page';
SignInPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<SignInCubit>(
create: (context) => SignInCubit(),
child: BlocListener<AuthCubit, AuthState>(
listenWhen: (AuthState previous, AuthState current) =>
previous.isUserSignedIn != current.isUserSignedIn &&
current.isUserSignedIn,
listener: (context, state) {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => BlocProvider.value(value: BlocProvider.of<SignInCubit>(context),child: HomePage(),),
));
},
child: Scaffold(body: signInPageWidget(context)),
),
);
}
}
my home page:
class HomePage extends StatelessWidget {
static const String id = 'home_page';
#override
Widget build(BuildContext context) {
return Scaffold(
body: MultiBlocListener(
listeners: [
BlocListener<AuthCubit, AuthState>(
listenWhen: (p, c) =>
p.isUserSignedIn != c.isUserSignedIn && !c.isUserSignedIn,
listener: (context, state) {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => SignInPage(),
));
}),
BlocListener<SignInCubit, SignInState>(
listenWhen: (p, c) =>
p.errorMessage != c.errorMessage && c.errorMessage != "",
listener: (context, state) {
print(state.errorMessage);
}),
],
child: BlocBuilder<SignInCubit, SignInState>(
builder: (context, SignInState state) {
return Center(
child: state.isInProgress
? CircularProgressIndicator()
: homePageBody(state, context)
);
},
),
));
}
}
BlocProvider automatically disposes of a bloc instance with context of new route instantiated, but that will not happen if you use BlocProvider.value:
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: ScreenA(),
);
Important note: BlocProvider.value should only be used for providing existing instances to new subtree, do not create Bloc instance with it, also if you want your BlocProvider to be in scope of whole app, wrap the root of your app, MaterialApp, and that will do the trick.
Also be sure to dispose of your bloc instance using PlocProvider.value, as it will not do it automatically.
I have 2 screens homepage and detailpage. I want to rebuild widget inside detailpage based on a certain action but the issue is the data come from homepage through http request and a specific object is passed to the second screen in purpose to display detail on it.
class ListPage extends StatefulWidget {
#override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
ParcelModel model = ParcelModel();
#override
void initState() {
model.loadData();
super.initState();
}
Widget build(BuildContext context) {
width = MediaQuery.of(context).size.width;
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ParcelModel()),
],
child: DefaultTabController(
length: 3,
child: ChangeNotifierProvider(
create: (context) => model,
child: Consumer<ParcelModel>(
builder:(context, model, child){
if(model.failed == true){
return Scaffold(
backgroundColor: Colors.white,
body:TabBarView(children:[
mescolis(context.watch<ParcelModel().listParcel),futureWidget_quotation(),
Payezcolisencoursdelivraison(context.watch<ParcelModel>().listParcel),
]),
floatingActionButton:FloatingActionButton(
onPressed: (){
MaterialPageRoute(
builder:(context) => ChangeNotifierProvider.value(
value:model,
child:Formtest(dataPays:context.watch<ParcelModel().listParcel,newsecontext:context,) //trying to pass the model here
)
);
}
)
),
),
);
Now I am trying to pass the model to the second screen inside a button but it does'nt work this is the function from the detail page when I press this it does not rebuild the homepage (screen 1)
new RaisedButton(
onPressed: (){
try{
Provider.of<ParcelModel>(newContext,listen: false).postData(
formData,
dio,
context,
nom_coli_Controller,
description_coli_Controller,
nature_Controller,
nom_coli_Controller
);
}catch(e){
FlutterFlexibleToast.showToast(
message: "error",
toastLength: Toast.LENGTH_LONG,
timeInSeconds:30,
radius: 70,
);
}
},
child: new Text("Press here"),),
I am using the flutter_bloc library to architect my app. In addition to the BlocProvider I am using the Repository Provider, since I will be using a specific repository extensively throughout my app. But I am having an issue with regards to context . Below is snippets of my code:
main.dart
void main() async {
.......
appRepository _appRepository = AppRepository();
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((_) {
runApp(
BlocProvider(
builder: (context) =>
AuthenticationBloc(appRepository: _appRepository)..dispatch(AppStarted()),
child: App(appRepository: _appRepository,),
),
);
});
}
class App extends StatelessWidget {
............
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (BuildContext context, AuthenticationState state) {
.....
if (state is AuthenticationUnauthenticated) {
return SafeArea(
top: false,
bottom: false,
child: RepositoryProvider(
builder: (context) => _appRepository,
child: LoginPage(firebaseMessaging: _firebaseMessaging),
),
);
}
......
},
),
);
}
}
Register button found in login form:
register_button.dart
class RegisterButton extends StatelessWidget {
final FirebaseMessaging _firebaseMessaging;
RegisterButton({
Key key,
#required FirebaseMessaging firebaseMessaging,
}) : assert(firebaseMessaging != null),
_firebaseMessaging = firebaseMessaging,
super(key: key);
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Don't have an account?", style: TextStyle(color: Colors.black)),
SizedBox(width: 4.0),
GestureDetector(
child: Text("Register here!",
style: TextStyle(
color: Color(0xFF585B8D), fontWeight: FontWeight.w500)),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
return RegisterPage(
firebaseMessaging: _firebaseMessaging,
);
}),
);
},
)
],
);
}
register_page.dart
class RegisterPage extends StatelessWidget {
final FirebaseMessaging _firebaseMessaging;
RegisterPage({
Key key,
#required FirebaseMessaging firebaseMessaging,
}) : assert(firebaseMessaging != null),
_firebaseMessaging = firebaseMessaging,
super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
builder: (context) => RegisterBloc(
appRepository: RepositoryProvider.of<AppRepository>(context),
firebaseMessaging: _firebaseMessaging,
),
child: RegisterForm(),
),
);
}
}
Question:
I'm getting an error when I click on the register button on my login form that says the following:
No ancestor could be found starting from the context that was passed to RepositoryProvider.of<AppRepository>().
This can happen if:
1. The context you used comes from a widget above the RepositoryProvider.
2. You used MultiRepositoryProvider and didn't explicity provide the RepositoryProvider types.
Good: RepositoryProvider<AppRepository>(builder: (context) => AppRepository())
Bad: RepositoryProvider(builder: (context) => AppRepository()).
The context used was: BlocProvider<RegisterBloc>(dirty, state: _DelegateWidgetState#a87b2(lifecycle state: created))
Why am I getting this error? This problem seems to be fixed if I put the repository provider as the child of the blocprovider and app as the child repository provider in the main function and then deleting the invidual repository providers in App(). I'm guessing the issue is from pushing the material page route from the button. I don't think I understand how context or provider exactly works in Flutter. I thought the provider would look up the widget tree for the repository/bloc, does pushing a route some how break this continuity?
When you use Navigator.of(context).push or Navigator.of(context).pushNamed the widget pushed is not a child of the widget that call Navigator.of(context).push or Navigator.of(context).pushNamed, this widget is a child of the closest instance of Navigator that encloses the given context, in your case the Navigator is created by the MaterialApp, so if you want to provide the Repository or Bloc to different routes, the Provider must be a parent of the Navigator, in your case must be a parent of MaterialApp.