I have a problem with bloc pattern and change notifier - flutter

I apply the same bloc pattern in the login and signup form,when i have only one provider, is not a problem. But now i implement second provider. This provider controller a page view, the index and other parameters. The problem is that i implement multiprovider, when i start login page i have an error.This is my code.
class MyApp extends StatelessWidget {
//final _prefs = new PreferenciasUsuario();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => new UiProvider()),
Provider(create: (_) => new BlocFormProvider())
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demos',
initialRoute: 'init',
routes: {
'init': (BuildContext context) => InitialPage(),
'login': (BuildContext context) => LoginPage(),
'signup': (BuildContext context) => SignUpPage(),
'home': (BuildContext context) => HomePage(),
'remember': (BuildContext context) => RememberPasswordPage(),
'start': (BuildContext context) => StartPage()
}),
);
}
}
and this is my bloc provider
import 'package:flutter/material.dart';
import 'login_bloc.dart';
class BlocFormProvider extends InheritedWidget {
static BlocFormProvider _instancia;
factory BlocFormProvider({Key key, Widget child}) {
if (_instancia == null) {
_instancia = new BlocFormProvider._internal(key: key, child: child);
}
return _instancia;
}
final loginBloc = LoginBloc();
BlocFormProvider._internal({Key key, Widget child})
: super(key: key, child: child);
#override
bool updateShouldNotify(covariant InheritedWidget oldWidget) => true;
static LoginBloc of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<BlocFormProvider>()
.loginBloc;
}
}
Finally this is the error:
The getter 'loginBloc' was called on null.
Receiver: null
Tried calling: loginBloc
The relevant error-causing widget was LoginPage
Thanks for your help.

Related

throw providernotfoundexception(t, context.widget.runtimetype);

I'm learning flutter and decided to work on a todo list application using cubit. I am created a cubit using bloc provider in the homescreen and in another screen I'm trying to consume the same cubit directly without creating another one.
Homescreen cubit section and creating database using cubit:
I created the cubit here and created the database.
class Homescreen extends StatelessWidget {
const Homescreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => appcubit()..CreateDatabase(),
child: BlocConsumer<appcubit, appStates>(
listener: (context, state) {
// ignore: todo
// TODO: implement listener
},
builder: (context, state) {
appcubit cubit = appcubit.get(context);
return Scaffold(
I have a button that directs to a second page:
Widget buildTaskCat(tasknum, cat, progress, context) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => cattaskview(
cat: cat,
progress: progress,
tasknum: tasknum,
),
),
);
},
On the second page Im trying to consume the cubit without using bloc provider. When I use bloc provider somehow I cant access the data in the database and I have to call create database again.
class cattaskview extends StatelessWidget {
const cattaskview(
{Key? key,
required this.cat,
required this.tasknum,
required this.progress})
: super(key: key);
final String cat;
final int tasknum;
final double progress;
#override
Widget build(BuildContext context) {
return BlocConsumer<appcubit, appStates>(
listener: (context, state) {
// TODO: implement listener
},
builder: (context, state) {
return Scaffold(
I get this error message when I try to run
if (inheritedElement == null && null is! T) {
throw ProviderNotFoundException(T, context.widget.runtimeType);
}
return inheritedElement;
}
Have you tried using the BlocProvider.value() Widget?
For example:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BlocProvider.value(
value: BlocProvider.of<appcubit>(context)
child: cattaskview(),
)
)
);
I fixed the issue by creating the cubit before material app
void main() {
Bloc.observer = MyBlocObserver();
runApp(const Todo());
}
class Todo extends StatelessWidget {
const Todo({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => appcubit()..CreateDatabase(),
child: BlocConsumer<appcubit, appStates>(listener: (context, state) {
// TODO: implement listener
}, builder: (context, state) {
return MaterialApp(
theme: ThemeData(
appBarTheme: const AppBarTheme(
systemOverlayStyle: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
),
),
),
home: Homescreen(),
debugShowCheckedModeBanner: false);
}));
}
}

How to pass parameter runApp(MyApp());

I have code as below, it's no problems.
void main() async {
Widget _defaultHome = new LoginPage();
runApp(new MaterialApp(
title: 'App',
debugShowCheckedModeBanner: false,
home: _defaultHome,
routes: <String, WidgetBuilder>{
// Set routes for using the Navigator.
'/tabs': (BuildContext context) => new TabsPage(),
'/login': (BuildContext context) => new LoginPage()
},
));
}
I want to change it to code as below, how can I pass _defaultHome to class MyApp?
void main() async {
Widget _defaultHome = new LoginPage();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
// final textTheme = Theme.of(context).textTheme;
return MaterialApp(
title: 'Gaia',
debugShowCheckedModeBanner: false,
home: _defaultHome,
routes: <String, WidgetBuilder>{
// Set routes for using the Navigator.
'/tabs': (BuildContext context) => new TabsPage(),
'/login': (BuildContext context) => new LoginPage()
},
);
}
}
Use the below code and any parameter can pass through constructor.
void main() async {
Widget _defaultHome = new LoginPage();
runApp(MyApp(defaultHome: _defaultHome,));
}
class MyApp extends StatelessWidget {
final Widget defaultHome;
const MyApp({
#required this.defaultHome,
Key key,
}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
// final textTheme = Theme.of(context).textTheme;
return MaterialApp(
title: 'Gaia',
debugShowCheckedModeBanner: false,
home: defaultHome,
routes: <String, WidgetBuilder>{
// Set routes for using the Navigator.
'/tabs': (BuildContext context) => new TabsPage(),
'/login': (BuildContext context) => new LoginPage()
},
);
}
}
You can either decide to pass it via Provider or via parameters.
This is the way you pass it via parameters with optional parameters:
void main() async {
final Widget _defaultHome = new LoginPage();
runApp(MyApp(homePage: _defaultHome));
}
class MyApp extends StatelessWidget {
MyApp({this.homePage});
final Widget homePage;
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
// final textTheme = Theme.of(context).textTheme;
return MaterialApp(
title: 'Gaia',
debugShowCheckedModeBanner: false,
home: _defaultHome,
routes: <String, WidgetBuilder>{
// Set routes for using the Navigator.
'/tabs': (BuildContext context) => new TabsPage(),
'/login': (BuildContext context) => homePage
},
);
}
}
You can pass it by a parameters in your constructor and assign it to your home.
class MyApp extends StatelessWidget {
// This widget is the root of your application.
final Widget defaultHome;
const MyApp({Key key, #required this.defaultHome}) : super(key: key);
#override
Widget build(BuildContext context) {
// final textTheme = Theme.of(context).textTheme;
return MaterialApp(
title: 'Gaia',
debugShowCheckedModeBanner: false,
home: defaultHome,
routes: <String, WidgetBuilder>{
// Set routes for using the Navigator.
'/tabs': (BuildContext context) => new TabsPage(),
'/login': (BuildContext context) => new LoginPage()
},
);
}
}

Why is the BlocBuilder never called when bloc is not set explicitly

This is an example of a Flutter counter app. I instantiate the Counter with a Bloc like this:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Counter(CounterBloc()),
);
}
}
This code below works fine. Event is dispatched and the "builder" method is called.
class Counter extends StatelessWidget {
final Bloc bloc;
const Counter(this.bloc, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (context) => bloc,
child: CounterPage(),
);
}
}
class CounterPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) => CountView(count),
),
floatingActionButton: AddButton(
action: () => BlocProvider.of<CounterBloc>(context)
.add(CounterEvent.increment)),
);
}
}
The code below does not work. The event is dispatched but the builder is never called.
class Counter extends StatelessWidget {
final Bloc bloc;
const Counter(this.bloc, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (context) => bloc,
child: Scaffold(
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) => CountView(count),
),
floatingActionButton: AddButton(
action: () => BlocProvider.of<CounterBloc>(context)
.add(CounterEvent.increment)),
),
);
}
}
I found out that I can set property "bloc" on a "BlocBuilder" but I'd expect it's not necessary.
Why the difference in behavior?
I believe the CounterEvent.increment from not working snippet won't get dispatched and instead will throw an error BlocProvider.of() called with a context ... because you use the same context where you provided the bloc.
This code works because it's a new context after BlocProvider
class Counter extends StatelessWidget {
final Bloc bloc;
const Counter(this.bloc, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (context) => bloc,
child: Builder(
builder: (context) => Scaffold(
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) => CountView(count),
),
floatingActionButton: AddButton(
action: () => BlocProvider.of<CounterBloc>(context)
.add(CounterEvent.increment),
),
),
),
);
}
}
This code also works because we explicitly use the bloc instance from the constructor instead of calling BlocProvider.of() and using the bloc instance provided via BlocProvider.
class Counter extends StatelessWidget {
final Bloc bloc;
const Counter(this.bloc, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (context) => bloc,
child: Scaffold(
body: BlocBuilder<CounterBloc, int>(
bloc: bloc,
builder: (context, count) => CountView(count),
),
floatingActionButton: AddButton(
action: () => bloc.add(CounterEvent.increment),
),
),
);
}
}
Both snippets above will work but it's not exactly the "correct" way.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Counter(
CounterBloc(), // <=() You need a work around to dispose this instance
),
);
}
}

Is there anyway that I can use MultiBlocProvider to clean this nest of widgets up?

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: '/'),
);
},
),
);
}
}

What is onUnknownRoute in flutter

I'm new to flutter, In MaterailApp Widget have an attribute called onUnknownRoute. What is the main use of onUnknownRoute?
Thanks for your help!
You can copy paste run full code below
In flutter web, when user manually key a undefine route, it can produce like 404 effect
full code
import 'package:flutter/material.dart';
void main() {
runApp( MaterialApp(
initialRoute: "/screen1",
routes: <String, WidgetBuilder>{
'/screen1': (BuildContext context) => Screen1(),
'/screen2': (BuildContext context) => Screen2(),
'/screen3': (BuildContext context) => Screen3(),
'/screen4': (BuildContext context) => Screen4()
},
onUnknownRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(
settings: settings,
builder: (BuildContext context) =>
Scaffold(body: Center(child: Text('Not Found'))),
);
},
)
);
}
class Screen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container();
}
}
class Screen2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container();
}
}
class Screen3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container();
}
}
class Screen4 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container();
}
}
onUnknownRoute property is used to handle the worst case when the app navigation fails to get a route. If not handled, exception will be generated.
The navigator widget manages a stack of Route objects.
Route objects are nothing but full-screen elements like "screens" or "pages".
The navigator manages navigation between the screens in any app.
The Navigator for any app is only built if routes are provided by one of below items
home
routes
onGenerateRoute
onUnknownRoute
If the app only has one page, then you can specify it using home.
Else all routes will be in routes table including the home
If a route is requested that is not specified in routes table (or by home), then the onGenerateRoute callback is called
when even onGenerateRoute fails to generate a route, the property OnUnknownRoute will be triggered to handle the scenario.
We can handle an unknown route as below,
return MaterialApp(
title: 'Named Routing',
onGenerateRoute: router.generateRoute,
onUnknownRoute: (settings) => MaterialPageRoute(
builder: (context) => UndefinedView(
name: settings.name,
)),
initialRoute: HomeViewRoute,
);
If unknown route event is not handled using the onUnknownRoute property, flutter may throw an exception
The solution is coding in routes -> Example
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Material App',
// home: HomePage(),
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => HomePage(),
'alert': (BuildContext context) => AlertPage(),
'avatar': (BuildContext context) => AvatarPage(),
},
***onUnknownRoute: (settings) {},***
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (BuildContext context) => AlertPage());
},
);
}
}