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.
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 have two screens in my flutter application Screen1 and Screen2. Screen1 is the home screen. I navigate from Screen1 to Screen2 via
Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder: (context, animation, secondaryAnimation) => Screen2());
and Screen2 to Screen1 via
Navigator.pop(context);
Screen1 is statelesswidget:
class Screen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<BlocA>(create: (_) => BlocA()),
BlocProvider<BlocB>(create: (_) => BlocB()),
]
child: RaisedButton(
child: Text('Goto Screen 2'),
onPressed: Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder: (context, animation, secondaryAnimation) => Screen2());
),
)
}
}
I would appreciate anyone can provide an answer that will satisfy the following :
Want to access the two bloc initialised in the Screen1 from Screen2 using
BlocProvider.value(value: BlocProvider.of(context), child: ...)
without bringing the initialisation of blocs upto the MaterialApp widget. Cannot make the MultiBlocProvider the parent of MaterialApp. I want the blocs only accessed in Screen1 and Screen2. It should not be accessed by other screens.
Also when popped from Screen2 to Screen1, the blocs should not be disposed. Hence, continue to maintain state when popped from Screen2
Should not pass the bloc via constructor or as arguments in Navigator
Currently getting following error:
flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building Screen2(dirty):
flutter: BlocProvider.of() called with a context that does not contain a BlocA.
flutter: No ancestor could be found starting from the context that was passed to
flutter: BlocProvider.of<BlocA>().
flutter:
flutter: This can happen if the context you used comes from a widget above the BlocProvider.
flutter:
flutter: The context used was: Screen2(dirty)
The use the already created bloc instance on new page, you can use BlocProvider.value.
Like passing BlocX to next route will be like
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<BlocX>(context),
child: Screen2(),
),
),
);
I might go for repository provider on your case. But to pass multiple instance, you can wrap BlocProvider two times on route.
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: BlocProvider.value(
value: BlocProvider.of<BlocB>(context),
child: Screen2(),
),
),
),
);
Currently, I cannot remember any better option, let me know if you've got any.
Now, your second route Screen2 can access both BlocB and BlocB instance.
You can get the instance it like, depend on your code structure.
BlocConsumer<BlocA, BlocAState>(
builder: (context, state) {
if (state is BlocAInitial) {
return Text(state.name);
}
return Text("un impleneted");
},
listener: (context, state) {},
),
When you create bloc, and like to pass it with BlocProvider.value(value: BlocProvider.of<BlocA>(context),, you need to use separate context.
More about blocprovider.
Check the demo, It will clarify, I am using Builder instead of creating new widget for context.
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Screen1(),
);
}
}
class Screen1 extends StatelessWidget {
const Screen1({super.key});
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<BlocA>(create: (_) => BlocA()),
BlocProvider<BlocB>(create: (_) => BlocB()),
],
child: Builder(builder: (context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: BlocProvider.value(
value: BlocProvider.of<BlocB>(context),
child: Screen2(),
),
),
),
);
},
),
);
}),
);
}
}
class Screen2 extends StatelessWidget {
const Screen2({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
BlocConsumer<BlocA, BlocAState>(
builder: (context, state) {
if (state is BlocAInitial) {
return Text(state.name);
}
return Text("un impleneted");
},
listener: (context, state) {},
),
BlocConsumer<BlocB, BlocBState>(
builder: (context, state) {
if (state is BlocBInitial) {
return Text(state.name);
}
return Text("un impleneted");
},
listener: (context, state) {},
),
],
),
);
}
}
Find more about flutterbloccoreconcepts
you have to elevate MultiBlocProvider in the widget tree so that it wraps both screens, e.g. make it a parent of MaterialApp
You can pass bloc elements as a parameter to Screen2
final blocAObject = BlocProvider.of<BlocA>(context);
Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder: (context, animation, secondaryAnimation) => Screen2(bloca:blocAObject));
If you're ok with initializing in MaterialApp while only having the blocs accessible from the two screens, try the following:
final blocA = BlocA(); // shared bloc instance
final blocB = BlocB(); // shared bloc instance
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'screen1': (_) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => blocA,
),
BlocProvider(
create: (context) => blocB,
),
],
child: Screen1(),
),
'screen2': (_) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => blocA,
),
BlocProvider(
create: (context) => blocB,
),
],
child: Screen2(),
),
},
);
}
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.
I'm using the BLOC pattern to authenticate a user in my app. I have a main BlocProvider that wraps my app. And a BlocBuilder to build according to the authentication state.
If the user is unauthenticated i have onboarding / intro screens that will navigate to the login screen.
The login screen is wrapped in another BlocProvider that contains a button that will do the login, and add a logged in event when the login is successful.
Problem is when i navigate from the onboarding screens i loose the main authenticationBloc context. What do i need to to to have access to the authentication bloc after i pushed a new screen.
void main() {
WidgetsFlutterBinding.ensureInitialized();
Bloc.observer = SimpleBlocObserver();
runApp(
MyApp(),
);
}
class AuthenticationWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider<AuthenticationBloc>(
create: (context) => AuthenticationBloc()..add(AppStarted()),
child: MyApp(),
),
);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state is Authenticated) {
_appUserProfileRepository = AppUserProfileRepository();
}
},
child: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
_authCredentialHelper = state.authCredentialHelper;
if (state is Uninitialized) {
return SplashScreen();
}
if (state is Unauthenticated) {
return OnboardingScreens(authCredentialHelper: _authCredentialHelper);
}
if (state is InvalidRegistration) {
return RegisterProfileScreen(authCredentialHelper: _authCredentialHelper);
}
if (state is Authenticated) {
xxx
}
return Scaffold(body: Center(child: LoadingIndicator()));
},
),
);
}
}
This is the onboarding screen where i loose the authenticationbloc context as soon as i navigate
class OnboardingScreens extends StatelessWidget {
final AuthCredentialHelper authCredentialHelper;
OnboardingScreens({this.authCredentialHelper});
_pages(BuildContext context) {
return [
xxx
];
}
_getStartedClicked(BuildContext context) {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return LoginScreen(authCredentialHelper: authCredentialHelper);
}));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: IntroductionScreen(
pages: _pages(context),
onDone: () => _getStartedClicked(context),
showSkipButton: true,
done: xxx
),
),
);
}
}
When adding a breakpoint at 1. the context is fine with a valid value for BlocProvider.of(context)
Stepping to 2. gives me an error:
BlocProvider.of() called with a context that does not contain a Cubit of type AuthenticationBloc.
_getStartedClicked(BuildContext context) {
1----->Navigator.push(context, MaterialPageRoute(builder: (context) {
2----->return LoginScreen(authCredentialHelper: authCredentialHelper);
}));
}
This is the LoginScreen code
class LoginScreen extends StatelessWidget {
final AuthCredentialHelper authCredentialHelper;
LoginScreen({this.authCredentialHelper});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back, color: darkBlue),
onPressed: () => Navigator.of(context).pop(),
),
backgroundColor: Colors.transparent,
elevation: 0.0,
),
body: SafeArea(
child: Center(
child: BlocProvider<LoginBloc>(
create: (context) => LoginBloc(authCredentialHelper: authCredentialHelper),
child: LoginForm(authCredentialHelper: authCredentialHelper),
),
),
),
);
}
}
Getting this error:
The following assertion was thrown building _InheritedProviderScope<LoginBloc>(value: Instance of 'LoginBloc'):
BlocProvider.of() called with a context that does not contain a Cubit of type AuthenticationBloc.
No ancestor could be found starting from the context that was passed to BlocProvider.of<AuthenticationBloc>().
This can happen if the context you used comes from a widget above the BlocProvider.
Change this :
Navigator.push(context, MaterialPageRoute(builder: (context) {
return LoginScreen(authCredentialHelper: authCredentialHelper);
}));
to
Navigator.push(
context,
MaterialPageRoute(builder: (contextLoginScreen) {
return BlocProvider.value(
value: context.bloc<AuthenticationBloc>(),
child: LoginScreen(authCredentialHelper: authCredentialHelper));
}),
);
Stream builder is used to draw widget based on data from stream.
What is the right way to achieve navigation based on the data?
Details:
There is a logout button in drawer. It clears the session and emits a data in the stream.
There's a stateless widget with stream builder listening on data and updating UI. How to make it navigate to login screen based on data in the stream?
In your stateless widget's build method, you can listen changes in your stream with listen() method.
Widget build(BuildContext context) {
Repository.bulletins.listen((pet) {
pet.documents[pet.documents.length - 1].data['animalType'] == "Dog"
? Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LostPetForm(),
))
: print('not yet');
});
return Scaffold(...
Inspired from https://stackoverflow.com/a/54109955/1918649
In the build method of the widget that creates Profile
#override
Widget build(BuildContext context) {
final userBloc = BlocProvider.of<UserBloc>(context);
return ...
somewhere here Profile(userBloc)
...
}
class Profile extends StatefulWidget {
final userBloc;
Profile(this.userBloc);
#override
State<StatefulWidget> createState() => ProfileState();
}
class ProfileState extends State<Profile> {
#override
void initState() {
super.initState();
widget.userBloc.stream.listen((userData){
if(userData==null) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LandingPage(),
));
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: new Text("Profile"),
),
drawer: CustomDrawer(),
body: Center(
child: StreamBuilder<UserModel>(
initialData: widget.userBloc.user,
stream: widget.userBloc.stream,
builder: (ctx, snap) => snap.hasData?Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.network(snap.data?.imageUrl),
Text(snap.data?.username)
],
):Text('You are logged out'),
),
),
);
}
}