open database table in BLoC, dispose in MyApp - is this valid? - flutter

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();
}
}

Related

Flutter Bloc: difference between RepositoryProvider and RepositoryProvider.value

I know the difference between RepositoryProvider and RepositoryProvider.value: the first one creates the repository for you and the second one receives a repository that is already created.
Please see the difference between the 2 code blocks - the first one is okay, the second one gives the following error.
RepositoryProvider.of() called with a context that does not contain a repository of type AuthRepository.
No ancestor could be found starting from the context that was passed to RepositoryProvider.of<AuthRepository>().
This can happen if the context you used comes from a widget above the RepositoryProvider.
The context used was: HomeScreen(dirty)
I don't understand why code 2 gives an error.
Code 1: success
class MyApp {
void main() {
// 1) Let the RepositoryProvider create the AuthRepository
runApp(RepositoryProvider(
crate: (context) => AuthRepository(),
child: BlocProvider(
create: (context) => AuthCubit(authRepository: RepositoryProvider.of<AuthRepository>(context)),
child: const MaterialApp(
// 2) Show HomeScreen that will access the AuthRepository instance
home: HomeScreen()
)
)
)
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final authCubit = BlocProvider.of<AuthCubit>(context);
// 3) This will succeed
final authRepo = RepositoryProvider.of<AuthRepository>(context);
return Scaffold(
body: BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
return const Text(state.toString());
},
),
);
}
}
Code 2: error
class MyApp {
void main() {
// 1) Create a repository instance of AuthRepository
final authRepo = AuthRepository();
// 2) Add this AuthRepository instance to the RepositoryProvider.value
runApp(RepositoryProvider.value(
value: (context) => authRepo,
child: BlocProvider(
create: (context) => AuthCubit(authRepository: authRepo),
child: const MaterialApp(
// 3) Show HomeScreen that will access the AuthRepository instance
home: HomeScreen()
)
)
)
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final authCubit = BlocProvider.of<AuthCubit>(context);
// 4) This will fail
final authRepo = RepositoryProvider.of<AuthRepository>(context);
return Scaffold(
body: BlocBuilder<AuthCubit, AuthState>(
builder: (context, state) {
return const Text(state.authenticationStatus.toString());
},
),
);
}
}
The value should be a repository not a function.
Instead of
runApp(RepositoryProvider.value(
value: (context) => authRepo,
try
runApp(RepositoryProvider.value(
value: authRepo,

Provider's watch() dosen't catch changing state in flutter

I am studying, provider in flutter. I try to make login process by using beamer and provider.
If user's auth state that is dectected by Provider context.watch<AuthenticationNotifier>().isAuthenticated; is false, BeamGuard force user to go auth screen.
final _routerDelegate = BeamerDelegate(
guards: [
BeamGuard(
pathPatterns: ['/'],
check: (context, location) {
return context.watch<AuthenticationNotifier>().isAuthenticated;
},
beamToNamed: (origin, target) => '/auth',
)
],
locationBuilder: BeamerLocationBuilder(
beamLocations: [PostListLocations(), AuthLocations()]),
);
User click login button in auth screen, auth state change true. I checked user'auth state is changed in AuthScreen.
void attemptVerify(BuildContext context) {
var authNotifier = context.read<AuthenticationNotifier>();
authNotifier.setUserAuth(true);
logger.d(authNotifier.userState);
}
}
but provider in BeamGuard is not watch state change. user do not go to main page, stay in auth page. if i set user's auth state True, user go to directly main page.So I think beamer is not problem. I think Provider doesn't work. I cannot find my mistake. could you help me?
this is full code.
main.dart
final _routerDelegate = BeamerDelegate(
guards: [
BeamGuard(
pathPatterns: ['/'],
check: (context, location) {
return context.watch<AuthenticationNotifier>().isAuthenticated;
},
beamToNamed: (origin, target) => '/auth',
)
],
locationBuilder: BeamerLocationBuilder(
beamLocations: [PostListLocations(), AuthLocations()]),
);
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(375, 812),
builder: (BuildContext context, Widget? child) {
return ChangeNotifierProvider<AuthenticationNotifier>( //provider
create: (context) => AuthenticationNotifier(),
child: MaterialApp.router(
routeInformationParser: BeamerParser(),
routerDelegate: _routerDelegate,
),
);
},
);
}
}
auth_notifier.dart
import 'package:flutter/widgets.dart';
class AuthenticationNotifier extends ChangeNotifier {
bool _isAuthenticated = false;
bool get isAuthenticated => _isAuthenticated;
void setUserAuth(bool authState) {
_isAuthenticated = authState;
notifyListeners();
}
}
auth_scree.dart
class AuthScreen extends StatefulWidget {
const AuthScreen({Key? key}) : super(key: key);
#override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: Scaffold(
body: SingleChildScrollView(
child: ElevatedButton(
onPressed: () {
attemptVerify(context);
},
child: Text("button"),
),
)),
),
);
}
void attemptVerify(BuildContext context) {
var authNotifier = context.read<AuthenticationNotifier>();
authNotifier.setUserAuth(true);
}
}

BlocListener confusion

I am trying to make an app using flutter blocs, but I am having troubles with the BlocListener not being called and I can't figure out what I'm doing wrong.
Here is a minimalish code reproducing my issue:
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => AuthBloc(),
child: const AppView(),
);
}
}
/**************** APP VIEW **************/
class AppView extends StatefulWidget {
const AppView({Key? key}) : super(key: key);
#override
State<AppView> createState() => _AppViewState();
}
class _AppViewState extends State<AppView> {
final _navigatorKey = GlobalKey<NavigatorState>();
NavigatorState get _navigator => _navigatorKey.currentState!;
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: _navigatorKey,
builder: (context, child) {
print('App builder');
return BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
print('Bloc listener');
switch (state.status) {
case AuthStatus.authenticated:
_navigator.pushAndRemoveUntil<void>(
MaterialPageRoute(
builder: (context) {
return Center(
child: Column(
children: [
const Text('Home'),
ElevatedButton(
onPressed: () {
context.read<AuthBloc>().add(
const AuthStatusChanged(
AuthStatus.unauthenticated));
},
child: const Text('Log out'),
),
],
),
);
},
),
(route) => false,
);
break;
default:
_navigator.pushAndRemoveUntil<void>(
MaterialPageRoute(
builder: (context) {
return Center(
child: Column(
children: [
const Text('Login'),
ElevatedButton(
onPressed: () {
context.read<AuthBloc>().add(
const AuthStatusChanged(
AuthStatus.authenticated));
},
child: const Text('Log in'),
),
],
),
);
},
),
(route) => false,
);
break;
}
},
child: child,
);
},
onGenerateRoute: (_) => MaterialPageRoute(
builder: (context) {
return const Center(
child: Text('splash'),
);
},
),
);
}
}
/**************** AUTH BLOC CLASSES **************/
/**************** AUTH State **************/
enum AuthStatus { unknown, unauthenticated, authenticated }
class AuthState extends Equatable {
final AuthStatus status;
const AuthState._({
this.status = AuthStatus.unknown,
});
const AuthState.unknown() : this._();
const AuthState.authenticated() : this._(status: AuthStatus.authenticated);
const AuthState.unauthenticated()
: this._(status: AuthStatus.unauthenticated);
#override
List<Object?> get props => [status];
}
/**************** AUTH Event **************/
abstract class AuthEvent extends Equatable {
const AuthEvent();
#override
List<Object> get props => [];
}
class AuthStatusChanged extends AuthEvent {
final AuthStatus status;
const AuthStatusChanged(this.status);
#override
List<Object> get props => [status];
}
/**************** AUTH BLOC **************/
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthBloc() : super(const AuthState.unknown()) {
print('Bloc constructor');
on<AuthStatusChanged>(_onAuthStatusChanged);
}
_onAuthStatusChanged(
AuthStatusChanged event,
Emitter<AuthState> emit,
) async {
switch (event.status) {
case AuthStatus.unauthenticated:
return emit(const AuthState.unauthenticated());
case AuthStatus.authenticated:
return emit(const AuthState.authenticated());
default:
return emit(const AuthState.unknown());
}
}
}
When I launch the app I would expect the BlocListener to be called once but instead it sits on the splash page.
I used this tutorial to produce this code : https://bloclibrary.dev/#/flutterlogintutorial
Edit:
Thank you all for your insight, I didn't understand that the BlocListener won't fire an event on the initialState (RTFM I guess xD). Looking back at the tutorial I used, this is dealt with by the "Repository" that feeds a stream delayed on creation and the Bloc is listening for that stream to fire a change of state events. Reusing the same concept works for me!
BlocListener only trigger when state has changed. On application load you may want to trigger a bloc event to change the AuthBloc state.
This could be achieved by adding a bloc event in the initState() function and placing a breakpoint to see if the listener is being triggered.
https://pub.dev/documentation/flutter_bloc/latest/flutter_bloc/BlocListener-class.html

My changenotifierprovider is not updating. Not sure why

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).

How to update streamProvider stream based on other providers value

I want to update StreamProvider stream, based on the value of the Provider. I arrived at this solution.
return Provider<SelectedDay>.value(
value: selectedDay,
child: ProviderToStream<SelectedDay>(
builder: (_, dayStream, Widget child) {
return StreamProvider<DailyHomeData>(
initialData: DailyHomeData.defaultValues(DateTime.now()),
create: (_) async* {
await for (final selectedDay in dayStream) {
yield await db
.dailyHomeDataStream(
dateTime: selectedDay.selectedDateTime)
.first;
}
},
child: MainPage(),
);
},
),
);
This is the providerToStream class, which i copied from here Trouble rebuilding a StreamProvider to update its current data
class ProviderToStream<T> extends StatefulWidget {
const ProviderToStream({Key key, this.builder, this.child}) : super(key: key);
final ValueWidgetBuilder<Stream<T>> builder;
final Widget child;
#override
_ProviderToStreamState<T> createState() => _ProviderToStreamState<T>();
}
class _ProviderToStreamState<T> extends State<ProviderToStream> {
final StreamController<T> controller = StreamController<T>();
#override
void dispose() {
controller.close();
super.dispose();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
controller.add(Provider.of<T>(context));
}
#override
Widget build(BuildContext context) {
return widget.builder(context, controller.stream, widget.child);
}
}
And this is the error i get when i try to use it
type '(BuildContext, Stream<SelectedDay>, Widget) => StreamProvider<DailyHomeData>' is not a subtype of type '(BuildContext, Stream<dynamic>, Widget) => Widget'
Note: The code doesnt even work when i place a dummy widget inside the ProviderToStream widget.
child: ProviderToStream<SelectedDay>(
builder: (_, ___, Widget child) {
return child;
},
),
I also tried to force somehow the type in the builder, so that it is not dynamic, with no luck
child: ProviderToStream<SelectedDay>(
builder: (_, Stream<SelectedDay> dayStream, Widget child) {
return child;
},
),