I want to reuse one bloc per router widget, but each widget already have a bloc, how to insert another bloc to it?
void main() => runApp(App());
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
final _sharedBloc = SharedBloc();
final _oneBloc = OneBloc();
final _twoBloc = TwoBloc();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
routes: {
'/one': (context) => BlocProvider.value(
value: _oneBloc, // add _sharedBloc to OnePage widget.
child: OnePage(),
),
'/two': (context) => BlocProvider.value(
value: _twoBloc, // add _sharedBloc to TwoPage widget.
child: TwoPage(),
),
},
);
}
#override
void dispose() {
_counterBloc.close();
super.dispose();
}
}
You can use MultiBlocProvider to provide multiple BLoC's at once to the child, as stated in the flutter_bloc documentation, like so:
routes: {
'/one': (context) => MultiBlocProvider(
providers: [
BlocProvider.value(
value: _sharedBloc,
),
BlocProvider.value(
value: _oneBloc,
),
],
child: OnePage(),
),
// '/two' is similar
},
Related
Edit: (main.dart)
Added Sentry which actually starts the app
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _sentryDSN;
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = _sentryTracesSampleRate;
options.attachStacktrace = true;
options.enableAppLifecycleBreadcrumbs = true;
},
appRunner: () => runApp(const SplashScreen()),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}
New to flutter, creating a splash screen to an app that was built with MaterialApp but getting an error. HOw can I solve this without a onPress function
Error:
Exception has occurred.
FlutterError (Navigator operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.)
import 'package:flutter/material.dart';
import 'package:loopcycle/screens/loopcycle_main.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_navigateToMainApp();
}
void _navigateToMainApp() async {
await Future.delayed(const Duration(milliseconds: 2000), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoopcycleMainApp()));
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => const Center(
child: Text("test"),
)),
);
}
}
Thank you in advance.
EDIT: I changed the solution after you provided more information about the code.
This error is happening because you are using a context that does not have a Navigator in it, this is happening probrably because the widget that you are getting the context is parent of the MaterialApp() widget, to solve it you should create another widget that is a child of the MaterialApp() instead of using the parent widget, let me give you an example instead:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: GestureDetector(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SomeWidget(),
),
),
child: Container(
height: 300,
width: 300,
color: Colors.red,
),
),
);
}
}
This may give an error because you are using the context of a widget that is the parent of the MaterialApp() widget, to solve it just create another widget that is a child of MaterialApp().
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: AnotherWidget(),
);
}
}
class AnotherWidget extends StatelessWidget {
const AnotherWidget({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SomeWidget(),
),
),
child: Container(
height: 300,
width: 300,
color: Colors.red,
),
),
);
}
}
I was playing with your code, and fixed it for you, and there are basically two ways to solve it, you can create a MaterialApp() before calling the SplashScreen() in the runApp() function like so:
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:loopcycle/screens/loopcycle_main.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _sentryDSN;
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = _sentryTracesSampleRate;
options.attachStacktrace = true;
options.enableAppLifecycleBreadcrumbs = true;
},
appRunner: () => runApp(
const MaterialApp(
home: SplashScreen(),
),
),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_navigateToMainApp();
}
void _navigateToMainApp() async {
await Future.delayed(const Duration(milliseconds: 2000), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoopcycleMainApp()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) => const Center(
child: Text("test"),
)),
);
}
}
Or you can create an intermediate widget to hold the MaterialApp() and then inside this widget you can call SplashScreen(), like so:
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:loopcycle/screens/loopcycle_main.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _sentryDSN;
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = _sentryTracesSampleRate;
options.attachStacktrace = true;
options.enableAppLifecycleBreadcrumbs = true;
},
appRunner: () => runApp(const MyApp()),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: SplashScreen(),
);
}
}
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_navigateToMainApp();
}
void _navigateToMainApp() async {
await Future.delayed(const Duration(milliseconds: 2000), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoopcycleMainApp()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) => const Center(
child: Text("test"),
)),
);
}
}
In this second solution, the intermediate widget is the MyApp() widget, and in my opinion, I consider this solution as being the best one for your problem, because if you ever wanted to load a different screen based on the different states, for example:
If a user is signed in you load a home page, and if a user is not signed in you load a sign up page.
Doing this, or anything similar is much easier when you have this intermediate widget that holds the MaterialApp(), and you can even create some logic to integrate the Splash Screen too, but I don't know what you are trying to achieve, so pick the solution you find the best for your problem.
So I'm learning basics of bloc and I wanted to know how to change a text using a button without setstate(), this whole time ive been using setstate but I would like to know how to change a text after a button click using bloc, state, event.
This is what I have so far
Main.dart
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
BlocProvider(
create: (_) => SecscreenBloc(),
),
],
child: MaterialApp(
onGenerateRoute: Routes().onGenerateRoute,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SecScreen()),
);
}
}
SecScreen.dart
class SecScreen extends StatefulWidget {
const SecScreen({Key? key}) : super(key: key);
#override
State<SecScreen> createState() => _SecScreenState();
}
class _SecScreenState extends State<SecScreen> {
var numm = 1;
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SecscreenBloc()..add(LoadSecscreenEvent(numm)),
child: Scaffold(
appBar: AppBar(
title: Text("Bloc Increment"),
),
body: BlocBuilder<SecscreenBloc,SecscreenState>(
builder: (context,state){
if(state is SecScreenLoadedState){
return Column(
children: [
Text("Activity: ${state.number}"),
SizedBox(height: 30),
ElevatedButton(
onPressed: () => BlocProvider.of<SecscreenBloc>(context).add(LoadSecscreenEvent(
numm
)),
child: Icon(Icons.add),
),
],
);
}
return Container(
color: Colors.red,
);
}
),
),
);
}
}
SecScreen_event.dart
abstract class SecscreenEvent extends Equatable {
const SecscreenEvent();
}
class LoadSecscreenEvent extends SecscreenEvent{
final int number;
LoadSecscreenEvent(this.number);
#override
List<Object?> get props => [number];
}
SecScreen_state.dart
part of 'secscreen_bloc.dart';
abstract class SecscreenState extends Equatable {
const SecscreenState();
}
class SecscreenInitial extends SecscreenState {
#override
List<Object> get props => [];
}
class SecScreenLoadedState extends SecscreenState{
final int number;
SecScreenLoadedState(this.number);
#override
List<Object?> get props => [number];
}
secscreen_bloc.dart
class SecscreenBloc extends Bloc<SecscreenEvent, SecscreenState> {
SecscreenBloc() : super(SecscreenInitial()) {
on<LoadSecscreenEvent>((event, emit) {
if (event is LoadSecscreenEvent){
emit(SecScreenLoadedState(event.number + 1));
}
});
}
}
I've been stuck at this for an embarssingly long time, would appreciate some help!
actually it is changed every time you press but every time the value gone to the bloc 1 and back 2 so you did not see the effection
just change the following line
onPressed: () => BlocProvider.of<SecscreenBloc>(context).add(LoadSecscreenEvent(
numm
)),
to this one :
onPressed: () => BlocProvider.of<SecscreenBloc>(context)
.add(LoadSecscreenEvent(state.number)),
So, I have made a function in Cubit Class it is to get data from API. For now I just can get the data if I pressed a button. I wanna make the function automatically called when the page/screen is open. For your information, this page is the first page that will be launched when user open the app. Here is some of my codes.
class UsersCubit extends Cubit<UsersState> {
UsersCubit() : super(UsersInitial());
UserRepository _userRepository = UserRepository();
void getAllUsers() async{
emit(UsersLoading());
try{
ResponseUsers _data = await _userRepository.getUsers();
emit(UsersSuccess(_data));
} catch(e){
emit(UsersError(e.toString()));
}
}
}
class UsersPage extends StatefulWidget {
const UsersPage({Key? key}) : super(key: key);
#override
_UsersPageState createState() => _UsersPageState();
}
class _UsersPageState extends State<UsersPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Users")),
body: BlocProvider(
create: (context) => UsersCubit(),
child: BlocConsumer<UsersCubit, UsersState>(
listener: (context, state) {
if(state is UsersLoading){
print("getting users ...");
} else if (state is UsersSuccess){
print(state.data.users[1].identity!.name);
} else if (state is UsersError){
print(state.errorMessage);
}
},
builder: (context, state) {
return Stack(
children: [
(state is UsersSuccess) ? listViewUsers(state.data.users) : progressBar(),
ElevatedButton(
onPressed: (){
context.read<UsersCubit>().getAllUsers();
},
child: Text("GET USERS"),
)
],
);
},
),
),
);
}
}
I have tried to call the function directly in initState but when I run the app it returns an error.
#override
void initState() {
context.read<UsersCubit>().getAllUsers();
super.initState();
}
error:
Error: Could not find the correct Provider<UsersCubit> above this UsersPage Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
Make sure that UsersPage is under your MultiProvider/Provider<UsersCubit>.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
...
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
...
consider using `builder` like so:
...
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
Is there any way to solve this case?
Can you try wrapp BlocConsumer inside a Builder ?
import 'package:flutter/material.dart';
class UsersPage extends StatefulWidget {
const UsersPage({Key? key}) : super(key: key);
#override
_UsersPageState createState() => _UsersPageState();
}
class _UsersPageState extends State<UsersPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Users")),
body: BlocProvider(
create: (context) {
final cubit = UsersCubit();
cubit.getAllUsers();
return cubit;
},
child: Builder(builder: (context) {
return BlocConsumer<UsersCubit, UsersState>(
listener: (context, state) {
if(state is UsersLoading){
print("getting users ...");
} else if (state is UsersSuccess){
print(state.data.users[1].identity!.name);
} else if (state is UsersError){
print(state.errorMessage);
}
},
builder: (context, state) {
return Stack(
children: [
(state is UsersSuccess) ? listViewUsers(state.data.users) : progressBar(),
ElevatedButton(
onPressed: (){
context.read<UsersCubit>().getAllUsers();
},
child: Text("GET USERS"),
)
],
);
},
);
}),
),
);
}
}
I have solved this case with help from #Ehsan Askari. He suggests me to provide the cubit above the MaterialApp, then I did it. Here is my code now
class AppWidget extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => UsersCubit(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: UsersPage(),
),
);
}
}
class UsersPage extends StatefulWidget {
const UsersPage({Key? key}) : super(key: key);
#override
_UsersPageState createState() => _UsersPageState();
}
class _UsersPageState extends State<UsersPage> {
#override
void initState() {
context.read<UsersCubit>().getAllUsers();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Users")),
body: BlocConsumer<UsersCubit, UsersState>(
listener: (context, state) {
if(state is UsersLoading){
print("getting users ...");
} else if (state is UsersSuccess){
print(state.data.users[1].identity!.name);
} else if (state is UsersError){
print(state.errorMessage);
}
},
builder: (context, state) {
return (state is UsersSuccess) ? listViewUsers(state.data.users) : progressBar();
},
),
);
}
}
you can call it from your Blocprovider by accessing your class like this :-
BlocProvider(
create: (context) => Usercubit()..getAllUsers(),
build : (context) => Scaffold() ........
You don't need to call a function inside initState when using Bloc or cubit, just call it when creating the Cubit inside BlocProvider like this >>.
class UsersPage extends StatefulWidget {
const UsersPage({Key? key}) : super(key: key);
#override
_UsersPageState createState() => _UsersPageState();
}
class _UsersPageState extends State<UsersPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Users")),
body: BlocProvider(
/// after create the cubit you can call the method.
create: (context) => UsersCubit()..getAllUsers();,
child: BlocConsumer<UsersCubit, UsersState>(
listener: (context, state) {
if(state is UsersLoading){
print("getting users ...");
} else if (state is UsersSuccess){
print(state.data.users[1].identity!.name);
} else if (state is UsersError){
print(state.errorMessage);
}
},
builder: (context, state) {
return Stack(
children: [
(state is UsersSuccess) ? listViewUsers(state.data.users) : progressBar(),
ElevatedButton(
onPressed: (){
context.read<UsersCubit>().getAllUsers();
},
child: Text("GET USERS"),
)
],
);
},
),
),
);
}
}
if we use MaterialApp, we build like this:
Navigator(
key: naviKey,
onGenerateRoute: (routeSettings) => MaterialPageRoute(
builder: (context)=>Container(),
),
)
and now, how to build if we use Getx?
Routing in GetX can be setup like so. Note Page1.id is after putting static const id = 'page_1 in Page1 so you don't have to use raw strings.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
home: Page1(),
routes: {
Page1.id: (context) => Page1(),
Page2.id: (context) => Page2(),
},
);
}
}
or like this
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
home: Page1(),
getPages: [
GetPage(name: Page1.id, page: () => Page1()),
GetPage(name: Page1.id, page: () => Page1()),
],
);
}
}
You can setup the list of routes on another page if you don't want to clutter up your GetMaterialApp with all your routes.
Then when you want to navigate you can do it like this
Get.to(Page2());
I'm trying to clean this mess of widgets up but I have found no way to do so. My NavigationBloc depends on the stream provided by AuthenticationBloc and to prevent memory leaks I have to close the stream.
The Builder widget is required so that I can get the latest BuildContext provided by BlocProvider but I know that MultiBlocProvider would clean this up tremendously. I'd like to avoid wrapping this widget in the runApp function but it's an option I guess.
class _MyAppState extends State<MyApp> {
final authRepo = AuthRepo();
AuthenticationBloc authBloc;
#override
void dispose() {
authBloc?.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocProvider<AuthenticationBloc>(
create: (_) =>
AuthenticationBloc(authRepo: authRepo)..add(InitializeAuth()),
child: Builder(builder: (context) {
authBloc = context.bloc<AuthenticationBloc>();
return BlocProvider<NavigationBloc>(
create: (_) => NavigationBloc(authBloc),
child: MaterialApp(
title: 'Arrow Manager',
debugShowCheckedModeBanner: false,
theme: appTheme(),
builder:
ExtendedNavigator<Router>(router: Router(), initialRoute: '/'),
),
);
}),
);
}
}
As you say, you can use the MultiProvider to avoid having nested providers
You have to create your AuthenticationBloc in the initState() method
class _MyAppState extends State<MyApp> {
final authRepo = AuthRepo();
AuthenticationBloc authBloc;
#override
void initState() {
super.initState();
authBloc = AuthenticationBloc(authRepo: authRepo);
}
#override
void dispose() {
authBloc?.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => authBloc..add(InitializeAuth()),
),
BlocProvider(
create: (context) => NavigationBloc(authBloc),
),
],
child: Builder(
builder: (context) {
authBloc = context.bloc<AuthenticationBloc>();
return MaterialApp(
title: 'Arrow Manager',
debugShowCheckedModeBanner: false,
theme: appTheme(),
builder: ExtendedNavigator<Router>(router: Router(), initialRoute: '/'),
);
},
),
);
}
}