Here is my change Notifier class.
class UserChamaNotifier with ChangeNotifier {
final List<UserChama> _userChamaList = [];
UnmodifiableListView<UserChama> get userchamaListy =>
UnmodifiableListView(_userChamaList);
void addUserChama(UserChama userchama) {
_userChamaList.add(userchama);
notifyListeners();
}
}
I have created the provider in main.dart:
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => _appStateManger,
),
ChangeNotifierProvider(
create: (context) => _profileManager,
),
ChangeNotifierProvider(
create: (context) => UserChamaNotifier(),
)
],
Then I proceed to add a chama object to my list:
UserChama userChama =
UserChama(id: s['Id'], phone: s['Phone'], name: s['Name']);
print(userChama.phone);
Provider.of<UserChamaNotifier>(context).addUserChama(userChama);
Here i try to access the list through the provider:
class ChamaList extends StatelessWidget {
const ChamaList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
UserChamaNotifier userChamaNotifier =
Provider.of<UserChamaNotifier>(context, listen: true);
return Text(userChamaNotifier.userchamaListy.length.toString());
}
}
At this point, i have experimented alot and i still don't have the correct way of implementation.
While adding data, set listen:false
Provider.of<UserChamaNotifier>(context,listen:false)
.addUserChama(userChama);
Check more how listen: false works when used with Provider.of(context, listen: false).
Related
I wanted to add theme with provider to my code. I adapted it from this source. https://github.com/lohanidamodar/flutter_theme_provider/blob/master/lib/main.dart .
Even it is same code, I got this error:
"The following ProviderNotFoundException was thrown building Home(dirty, state: _HomeState#c900c):
Error: Could not find the correct Provider above this Home Widget"
This happens because you used a BuildContext that does not include the provider
of your choice.
void main() async {
setPathUrlStrategy();
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MaterialAppWithTheme());
}
class MaterialAppWithTheme extends StatefulWidget {
#override
_MaterialAppWithThemeState createState() => _MaterialAppWithThemeState();
}
class _MaterialAppWithThemeState extends State<MaterialAppWithTheme> {
#override
void initState() {
super.initState();
AppRouter appRouter = AppRouter(
routes: AppRoutes.routes,
notFoundHandler: AppRoutes.routeNotFoundHandler,
);
appRouter.setupRoutes();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ThemeNotifier(),
child: Consumer<ThemeNotifier>(
builder: (context, ThemeNotifier notifier, child) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: notifier.darkTheme ? dark : light,
onGenerateRoute: AppRouter.router.generator,
);
},
),
);
}
}
Change this:
create: (_) => ThemeNotifier(),
To this:
create: (context) => ThemeNotifier(),
I'm fetching data from the local database using SQflite in my main.dart and passing it to ProvideRecords widget using FutureProvider :
Future<List<WeightRecord>> _getRecords() async {
List<WeightRecord> records = await RecordsDatabase.instance.getRecords();
return records;
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: FutureProvider<List<WeightRecord>?>(
create: (context) {
return _getRecords();
},
initialData: [],
catchError: (_, error) => [
WeightRecord(
date: DateTime.now(), weight: 00.0, note: 'hasError: $error')
],
child: ProvideRecords(),
),
);
}
}
then in the ProvideRecords widget I pass the data again to another provider :
class ProvideRecords extends StatelessWidget {
const ProvideRecords({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<List<WeightRecord>?>(builder: (context, list, child) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => RecordsListModel()),
ChangeNotifierProvider(create: (context) => ButtonMode())
],
builder: (context, child) {
Provider.of<RecordsListModel>(context, listen: true)
.updateRecordsList(list);
return Home(list: list);
});
}
});
}
}
the code works but I'm getting setState() or markNeedsBuild() called during build. because I'm using Provider.of<RecordsListModel>(context, listen: true).updateRecordsList(list); in the builder function. However I couldn't find another way of passing the data from FutureProvider to the RecordListModel, what can I do?
Its mainly an issue of how you have structured your code
Its recommended to have your multiproviders at the apps entry point..
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
//my providers here
ChangeNotifierProvider(create: (context) => ButtonMode())
... material app
then when adding data no need to set listen to true
Provider.of<RecordsListModel>(context, listen: false)
.updateRecordsList(list);
since home depends on data from the above provider use a consumer
Home(list: list);
// consume your provider
Consumer<RecordsListModel>(
builder:
(context, RecordsListModel recordsP, child) {
return Home(list:recordsP.list);
},
)
I am using the Hive database and would like to open a box (aka table) inside of a session BLoC. This appears to me more reasonable than to use a FutureBuilder in myApp() or alike.
Now, the hive team suggests to close a table upon app exit ("Before your application exits, you should call Hive.close() to close all open boxes."). Is it valid to do so or should opening and closing happen in the same widget for some (which) reason?
class App extends StatelessWidget {
const App({
required this.authenticationRepository,
required this.userRepository,
required this.sessionRepository,
}) : super(key: key);
final AuthenticationRepository authenticationRepository;
final UserRepository userRepository;
final SessionRepository sessionRepository;
#override
Widget build(BuildContext context) {
return RepositoryProvider.value(
value: authenticationRepository,
child: MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => AuthenticationBloc(
authenticationRepository: authenticationRepository,
userRepository: userRepository,
) ),
// *** IN THE BLOC BELOW I AM OPENING THE BOX AKA DATA TABLE WITH
// *** await Hive.openBox('problemMasterData');
BlocProvider(
create: (_) => SessionBloc()
),
],
child: AppView(),
));
}
}
class AppView extends StatefulWidget {
#override
_AppViewState createState() => _AppViewState();
}
class _AppViewState extends State<AppView> {
#override
Widget build(BuildContext context) {
return PlatformApp(
cupertino: (_, __) => CupertinoAppData(theme: HomeThemeCupertino.lightHomeTheme),
initialRoute: '/',
onGenerateRoute: AppRoutes.generateRoutes,
],
builder: (context, child) {
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
switch (state.status) {
case AuthenticationStatus.authenticated:
_navigator.pushNamedAndRemoveUntil('/home', (route) => false);
break;
case AuthenticationStatus.unauthenticated:
_navigator.pushNamedAndRemoveUntil('/login', (route) => false);
break;
default:
break;
}
},
child: child,
);
},
);
}
// *** AND HERE I WANT TO CLOSE THE BOX
#override
void dispose() {
Hive.box('problemMasterData');
super.dispose();
}
}
I am using Flutter_bloc package to work with bloc pattern in flutter, but i am wondering if it is a good practice to use a MultiBlocProvider inside main function and add all of my blocs in there like this:
void main()async{
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(Mafqood());
}
class Mafqood extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers : [
BlocProvider<AuthBloc>(
create: (context) => AuthBloc(AuthInitialState(), AuthRepository()),
),
BlocProvider<LoginBloc>(
create: (context) => LoginBloc(LoginInitialState(), AuthRepository()),
),
BlocProvider<ProfileBloc>(
create: (context) => ProfileBloc(ProfileInitialState(), AuthRepository()),
),
],
child: MaterialApp(
or it is better to add the bloc just where I need it? and why?
Thanks in advance.
You should use MultiBlocProvider inside the main function as you did. This is the best practice. And this is the goal of the providers.
Edit:
Now I realized that there is another answer here.
The main usage of MultiBlocProvider is using the bloc object in different places inside your application before you had to define which bloc depends on another bloc.
If you have an app that each screen use its own bloc, then you don't have a need for MultiBlocProvideras you can creat the bloc in the build function of the screen
class ParentScreen extends StatelessWidget {
const ParentScreen ({Key? key, this.data}) : super(key: key);
final data;
#override
Widget build(BuildContext context) {
return BlocProvider<MyBloc>(
create: (_) => MyBloc()),
child: MyScreenBody());
}
}
Class MyScreenBody extends StatefulWidget {
const MyScreenBody({Key? key}) : super(key: key);
#override
_MyScreenBodyState createState() => _MyScreenBodyState();
}
class _MyScreenBodyState extends State<MyScreenBody> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<MyBloc, MyState>(
builder: (context, state) {
return //... your code
}
)
);
}
In my app, I have a model that store the user logged in my app.
class AuthenticationModel extends ChangeNotifier {
User _user;
User get user => _user;
void authenticate(LoginData loginData) async {
// _user = // get user from http call
notifyListeners();
}
void restoreUser() async {
//_user = // get user from shared prefs
notifyListeners();
}
}
The model is registered at the top of the widget tree :
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthenticationModel(),
child: MaterialApp(
title: 'My App',
initialRoute: '/',
routes: {
'/': (context) => PrehomeScreen(),
'/home': (context) => HomeScreen()
},
),
);
}
}
Somewhere down the widget tree, I have a button that calls the Model :
child: Consumer<AuthenticationModel>(
builder: (context, authModel, child) {
return MyCustomButton(
text: 'Connect',
onPressed: () {
authModel.authenticate(...)
},
);
},
),
Now, I would like, somewhere, listen to the changes on the AuthenticationModel to trigger a Navigator.pushReplacmentNamed('/home') when the user is not null in the model.
I tried to do it in the builder of Prehome :
class PrehomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<AuthenticationModel>(
builder: (context, authModel, child) {
if (authModel.user != null) {
Navigator.of(context).pushReplacementNamed("/home")
}
return Container(
child: // Prehome UI
);
},
);
}
}
but I have a error when doing it like this :
════════ (2) Exception caught by widgets library ═══════════════════════════════════════════════════
setState() or markNeedsBuild() called during build.
The relevant error-causing widget was:
Consumer<AuthenticationModel> file:///Users/pierre.degand/Projects/cdc/course_du_coeur/lib/Prehome.dart:13:12
═══════════════════════════════════════════════════════════════════════════════
How can I setup such a listener ? Is it a good practice to trigger navigation on model changes like this ?
Thanks
EDIT: I found a way to make this work. Instead of using Consumer inside the PrehomeScreen builder, I used the following code :
class PrehomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
Provider.of<AuthenticationModel>(context).addListener(() {
Navigator.of(context).pushReplacementNamed("/home");
});
return Container(
child: // UI
);
}
}
It works fine, the navigation is executed when the model changes. But there is an error message in the console (printed 3 times) :
════════ (4) Exception caught by foundation library ════════════════════════════════════════════════
Looking up a deactivated widget's ancestor is unsafe.
════════════════════════════════════════════════════════════════════════════════════════════════════
The app does not crash so, for now, I'm ok with this.
I still want to know if this is a good approach or not.
I prefer to use Stream or rxdart PublishSubject BehaviourSubject for listening to any activity or to manage global app data.
I implement it using bloc pattern. Basically bloc pattern is just like redux for react means creating a central dataset that contains all app data and you don't have to do prop drilling.
You can create Stream like this.
import 'package:rxdart/rxdart.dart';
class AbcBloc {
BehaviorSubject<bool> _connectivity;
AbcBloc() {
_connectivity = BehaviorSubject<bool>();
}
// stream
Stream<bool> get connectivity => _connectivity.stream;
// sink
Function(bool) get updateConnectivity => _connectivity.sink.add;
dispose(){
_connectivity.close();
}
}
void createAbcBloc() {
if (abcBloc != null) {
abcBloc.dispose();
}
abcBloc = AbcBloc();
}
AbcBloc abcBloc = AbcBloc();
now you can access that abcBloc variable from anywhere and listen to connectivity variable like this
import './abcBloc.dart';
void listenConnectivity(){
abcBloc.connectivity.listen((bool connectivety){
here you can perform your operations
});
}
and you can update connectivity from abcBloc.updateConnectivity(false);
every time you perform any changes that listener will get called.
remember you have to call listenConnectivity() one time to get it activated;
void main() {
Provider.debugCheckInvalidValueType = null;
return runApp(
Provider(
create: (_) => AuthenticationModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final navigatorKey = GlobalKey<NavigatorState>();
Provider.of<AuthenticationModel>(context).addListener(() {
final authModel = Provider.of<AuthenticationModel>(context);
if (authModel.user != null) {
navigatorKey.currentState.pushReplacementNamed("/home");
}
});
return MaterialApp(
navigatorKey: navigatorKey,
title: 'My App',
initialRoute: '/',
routes: {
'/': (context) => PrehomeScreen(),
'/home': (context) => HomeScreen()
},
);
}
}
I don't think ChangeNotifier is needed.
void main() async {
final isLoggedIn = await Future.value(true); // get value from shared prefs or your model
runApp(MyApp(isLoggedIn));
}
class MyApp extends StatelessWidget {
MyApp(this.isLoggedIn);
final bool isLoggedIn;
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: isLoggedIn ? '/home' : '/',
routes: {
'/': (context) => HomeScreen(),
'/login': (context) => LoginScreen()
},
);
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text('Logout'),
onPressed: () => Navigator.of(context).pushReplacementNamed("/login"),
);
}
}
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text('Login'),
onPressed: () => Navigator.of(context).pushReplacementNamed("/"),
);
}
}