I have an issue with the auto dispose change notifier provider
I have created a widget to handle screen state to init data then show loading and after loading the screen will appear.
class ConsumerHelper extends StatefulWidget {
const ConsumerHelper({
super.key,
this.onInit,
this.onDispose,
this.onReady,
required this.child,
});
final Widget child;
final Future? onInit;
final VoidCallback? onDispose;
final VoidCallback? onReady;
#override
State<StatefulWidget> createState() => _ConsumerHelperState();
}
class _ConsumerHelperState extends State<ConsumerHelper> {
bool isLoading = true;
#override
void initState() {
Future.microtask(() async {
if (widget.onInit != null) {
await widget.onInit!;
if (widget.onReady != null) {
widget.onReady!();
}
}
isLoading = false;
if (mounted) {
setState(() {});
}
});
super.initState();
}
#override
void dispose() {
if (widget.onDispose != null) {
widget.onDispose!();
}
super.dispose();
}
#override
Widget build(BuildContext context) {
if (isLoading) {
return const Material(child: LoadingWidget());
} else {
return widget.child;
}
}
}
But when I'm using ref.read in the ConsumerHelper onInit parameter then use ref.watch inside the new ConsumerHelper child it throws an error
A was used after being disposed. Once you have called dispose() on A, it can no longer be used
here is how i use the ConsumerHelper Widget
class CategoriesPage extends ConsumerWidget {
const CategoriesPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, ref) {
final controller = ref.read(categoriesProvider);
return WillPopScope(
onWillPop: () async {
ref.read(pagesHandlerProvider.notifier).setIndex = 0;
return false;
},
child: ConsumerHelper(
onInit: controller.getCategories(),
child: Scaffold(
I'm trying to display a document field value from firestore and want to display it on other pages using the provider.
This is my code inside the provider file:
class UserState extends ChangeNotifier {
String userName = 'default error';
Future<void> getName() async {
await FirebaseFirestore.instance
.collection("Users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.get()
.then((value) {
userName = (value.data()?[' uname'] ?? "Default userName");
print(userName);
});
notifyListeners();
}
}
Here the correct userName value is getting printed using print statement, but when I try to pass it through the provider it is showing the initialized string value default error which I provided for null safety.
This is the screen where I want to display the variable userName :
class testscreen extends StatefulWidget {
const testscreen({Key? key}) : super(key: key);
_testscreenState createState() => _testscreenState();
}
class _testscreenState extends State<testscreen> {
#override
Widget build(BuildContext context) {
Provider.of<UserState>(context, listen: false).getName();
final String name = Provider.of<UserState>(context).userName;
return Scaffold(body: Text(name));
}
}
How can I show the right value instead of initialized value for userName?What's wrong with my code?
There are some fixes in the code.
notifyListeners() are not waiting for the fetch to complete
await FirebaseFirestore.instance
.collection("Users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.get()
.then((value) {
userName = (value.data()?[' uname'] ?? "Default userName");
print(userName);
notifyListeners(); š Try adding the notifyListeners() here
});
}
Remove the provider() call from within the bluid method
class testscreen extends StatefulWidget {
const testscreen({Key? key}) : super(key: key);
_testscreenState createState() => _testscreenState();
}
class _testscreenState extends State<testscreen> {
#override
void initState() {
super.initState();
Provider.of<UserState>(context, listen: false).getName();š Call getName here
}
#override
Widget build(BuildContext context) {
return Scaffold(body: Text( Provider.of<UserState>(context).userName)); š Call it this way
}
}
notifyListeners doesn't wait to finish the fetch. You can try
void getName() {
FirebaseFirestore.instance
.collection("Users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.get()
.then((value) {
userName = (value.data()?[' uname'] ?? "Default userName");
print(userName);
notifyListeners();
});
}
Or
Future<void> getName() async {
final value = await FirebaseFirestore.instance
.collection("Users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.get();
userName = (value.data()?[' uname'] ?? "Default userName");
notifyListeners();
}
I will prefer using Consumer widget for this case.
You can also use initState to call the method.
class testscreen extends StatefulWidget {
const testscreen({Key? key}) : super(key: key);
_testscreenState createState() => _testscreenState();
}
class _testscreenState extends State<testscreen> {
#override
void initState() {
super.initState();
Provider.of<UserState>(context, listen: false).getName();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<UserState>(// using consumer
builder: (context, value, child) {
return Text(value.userName);
},
),
);
}
}
Also you can call the method on constructor,
class UserState extends ChangeNotifier {
String userName = 'default error';
UserState() {
getName();
}
Future<void> getName() async {
await Future.delayed(Duration(seconds: 1));
userName = "Default userName";
notifyListeners();
}
}
Iām fetching data from firebase but i have this error
Unhandled Exception: Bad state: field does not exist within the DocumentSnapshotPlatform
the fields are correct and there is no difference i checked them
this is my method
static final List<ProductModels> _products = [];
Future<void> fetchData() async {
await FirebaseFirestore.instance
.collection('products')
.get()
.then((QuerySnapshot productsSnapshot) {
for (var element in productsSnapshot.docs) {
_products.insert(
0,
ProductModels(
id: element.get('id'),
title: element.get('title'),
imageUrl: element.get('imageUrl'),
productcat: element.get('productCategoryName'),
price: element.get('price'),
salePrice: element.get('salePrice'),
isOnSale: element.get('isOnSale'),
size: element.get('Size'),
color: element.get('Color'),
));
}
});
notifyListeners();
}
and i call it here
class FetchScreen extends StatefulWidget {
const FetchScreen({Key? key}) : super(key: key);
#override
State<FetchScreen> createState() => _FetchScreenState();
}
class _FetchScreenState extends State<FetchScreen> {
#override
void initState() {
Future.delayed(Duration(microseconds: 5),() async{
final productsProvider = Provider.of<ProductProvider>(context,listen: false);
await productsProvider.fetchData();
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context)=> BtmNavBarScreen()));
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
there is no difference between the firebase and the code i checked each one
I'm new to Bloc ( coming from provider ), everything works fine except whenever i try to add a second event to the same bloc on the same widget it never gets triggered, even the mapEventToState doesn't trigger,
i first add an event to get posts in the initState, this works fine, later on i add event on the refresh indicator onRefresh function, everything inside the function triggers Except the bloc add event.
I'm using Equatable, so i tried without it and no difference .
i tried to make separate class for each state, still Not working as well .
i tried Not to close the bloc on dispose, also not working.
ForumMain widget is a child of a BottomNavigationTabBar
here is my code :
main.dart
List<BlocProvider<Bloc>> _blocProviders() => [
BlocProvider<UserBloc>(
create: (context) => UserBloc(userRepository: RepositoryProvider.of(context),navigator: RepositoryProvider.of(context),prefs: RepositoryProvider.of(context),
),
),
BlocProvider<ForumBloc>(
create: (context) => ForumBloc( RepositoryProvider.of(context),_userBloc, RepositoryProvider.of(context),),
),
BlocProvider<WUpdateBloc>(
create: (context) => WUpdateBloc(
RepositoryProvider.of(context),
RepositoryProvider.of(context)),
),
BlocProvider<PlanBloc>(
create: (context) => PlanBloc(RepositoryProvider.of(context),RepositoryProvider.of(context),
)),
];
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return OrientationBuilder(builder: (context, orientation) {
return RepositoryProvider(
create: (context) => _dioInstance(),
child: MultiRepositoryProvider(
providers: _repositoryProviders(),
child: MultiBlocProvider(
providers: _blocProviders(),
child: Builder(
builder: (context) => MaterialApp(
// theme: AppTheme.theme,
navigatorKey: navigatorKey,
navigatorObservers: [appNavigatorObserver],
localizationsDelegates: _getLocalizationsDelegates(),
supportedLocales: S.delegate.supportedLocales,
home: LoadingPage(),
debugShowCheckedModeBanner: false,
),
)),
),
);
});
});
}
forum_bloc.dart
class ForumBloc extends Bloc<ForumEvent, ForumState> {
ForumBloc(this._forumRepository, this._userBloc, this.navigator) : super(ForumState.defaultState());
final ForumRepository _forumRepository;
final UserBloc _userBloc;
final AppNavigator navigator;
#override
Stream<ForumState> mapEventToState(
ForumEvent event,
) async* {
if (event is GetPostsEvent) yield* _getPosts(event);
if (event is GetCommentsEvent) yield* _getComments(event);
if (event is RefreshPostsEvent) yield* _refreshPosts(event);
if (event is RefreshCommentsEvent) yield* _refreshComments(event);
if (event is NewPostRequest) yield* _newPost(event);
if (event is NewCommentRequest) yield* _newComment(event);
}
Stream<ForumState> _getPosts(GetPostsEvent event) async* {
print("get posts event called");
yield state.copyWith(status: BlocStatus.pending);
try {
// show progress hud
final postsResponse = await _forumRepository.getPosts(event.trainerID);
postsResponse.posts.sort((a, b) => b.datetimeCreated.compareTo(a.datetimeCreated));
print(postsResponse.posts[0].question);
yield state.copyWith(posts: postsResponse.posts, status: BlocStatus.success, total: postsResponse.posts.length);
} on DioError catch (error) {
// report error
yield state.copyWith(status: BlocStatus.error, appError: error.toAppError());
}
}
Stream<ForumState> _refreshPosts(RefreshPostsEvent event) async* {
print("refresh posts event called");
try {
// show progress hud
final postsResponse = await _forumRepository.getPosts(event.trainerID);
print(postsResponse.posts.length);
postsResponse.posts.sort((a, b) => b.datetimeCreated.compareTo(a.datetimeCreated));
yield state.copyWith(posts: postsResponse.posts, status: BlocStatus.success, total: postsResponse.posts.length);
} on DioError catch (error) {
// report error
// yield state.copyWith(status: BlocStatus.error, appError: error.toAppError());
}
}
forum_state.dart
class ForumState extends Equatable {
ForumState({
this.total,
this.posts,
this.status,
this.appError
});
final int total;
final List<BlogPost> posts;
final BlocStatus status;
final AppError appError;
factory ForumState.defaultState() => ForumState(
total: 0,
posts: [],
status: BlocStatus.idle,
appError: null
);
ForumState copyWith({
int total,
List<BlogPost> posts,
AppError appError,
BlocStatus status, }) { return ForumState(
total: total ?? this.total,
posts: posts ?? this.posts,
status: status,
);}
#override
List<Object> get props => [posts,total,status,appError];
#override
bool get stringify => true;
}
forum_state.dart failed trial to make separate class for each state
class ForumState extends Equatable {
#override
// TODO: implement props
List<Object> get props => [];
}
class ForumStateLoading extends ForumState {
}
class ForumStateSuccess extends ForumState {
ForumStateSuccess({
this.total,
this.posts,
});
final int total;
final List<BlogPost> posts;
#override
List<Object> get props => [posts,total];
}
class ForumStateRefresh extends ForumState {
ForumStateRefresh({
this.total,
this.posts,
});
final int total;
final List<BlogPost> posts;
#override
List<Object> get props => [posts,total];
}
class ForumStateError extends ForumState {
ForumStateError({
this.error,
});
final AppError error;
#override
List<Object> get props => [error];
}
forum_event.dart
abstract class ForumEvent extends Equatable {
const ForumEvent();
#override
List<Object> get props => [];
}
class GetPostsEvent extends ForumEvent {
final String trainerID;
GetPostsEvent(this.trainerID);
}
class RefreshPostsEvent extends ForumEvent {
final String trainerID;
RefreshPostsEvent(this.trainerID);
}
class GetCommentsEvent extends ForumEvent {
final String postID;
final String trainerID;
GetCommentsEvent(this.postID,this.trainerID);
}
class RefreshCommentsEvent extends ForumEvent {
final String postID;
final String trainerID;
RefreshCommentsEvent(this.postID,this.trainerID);
}
class SendPostEvent extends ForumEvent {
final NewPostRequest postRequest;
SendPostEvent(this.postRequest);
}
class SendCommentEvent extends ForumEvent {
final NewCommentRequest commentRequest;
SendCommentEvent(this.commentRequest);
}
forum_screen.dart
class ForumMain extends StatefulWidget {
#override
_ForumMainState createState() => _ForumMainState();
}
class _ForumMainState extends State<ForumMain> {
TextEditingController nameController = TextEditingController();
MyTheme myTheme = MyTheme();
ForumBloc _forumBloc;
PlanBloc _planBloc;
Completer<void> _refreshCompleter;
#override
void initState() {
// TODO: implement initState
super.initState();
myTheme.initLoadingHUD();
_forumBloc = BlocProvider.of<ForumBloc>(context);
_planBloc = BlocProvider.of<PlanBloc>(context);
_forumBloc.add(GetPostsEvent(_planBloc.state.chosenOrder.trainer.id));
_refreshCompleter = Completer<void>();
}
#override
void dispose() {
_forumBloc.close();
_planBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
ScreenUtil.init(context, width: 375, height: 812, allowFontScaling: true);
return BlocConsumer<ForumBloc,ForumState>(
listener: (context, state) {
print(state.status.toString());
setState(() {
_refreshCompleter?.complete();
_refreshCompleter = Completer();
});
myTheme.errorBlocListener(context, state.appError);
},
cubit: _forumBloc,
builder: (context, state) {
return Scaffold(
backgroundColor: Colors.white,
appBar: ForumAppBar(
height: 80.h,
forum: true,
),
body: state.status == BlocStatus.success ? Stack(
children: [
RefreshIndicator(
onRefresh: () {
_forumBloc.add(RefreshPostsEvent(
_planBloc.state.chosenOrder.trainer.id));
return _refreshCompleter.future;
},
child: ListView(
children: state.posts.map((e) {
Please change your forum_event file like this:
abstract class ForumEvent extends Equatable {
const ForumEvent([List props = const []]) : super();
}
class GetPostsEvent extends ForumEvent {
final String trainerID;
GetPostsEvent(this.trainerID);
#override
List<Object> get props => [trainerID];
}
class RefreshPostsEvent extends ForumEvent {
final String trainerID;
RefreshPostsEvent(this.trainerID);
#override
List<Object> get props => [trainerID];
}
class GetCommentsEvent extends ForumEvent {
final String postID;
final String trainerID;
GetCommentsEvent(this.postID,this.trainerID);
#override
List<Object> get props => [postID, trainerID];
}
class RefreshCommentsEvent extends ForumEvent {
final String postID;
final String trainerID;
RefreshCommentsEvent(this.postID,this.trainerID);
#override
List<Object> get props => [postID, trainerID];
}
class SendPostEvent extends ForumEvent {
final NewPostRequest postRequest;
SendPostEvent(this.postRequest);
#override
List<Object> get props => [postRequest];
}
class SendCommentEvent extends ForumEvent {
final NewCommentRequest commentRequest;
SendCommentEvent(this.commentRequest);
#override
List<Object> get props => [commentRequest];
}
You can try instead of _blocProviders() just place the List of providers inside MultiBlocProvider( providers: [ ... ],.
I see. Please try removing the following code. I don't think you should close it in this widget, since this is not where you initialize it.
_forumBloc.close();
_planBloc.close();
I want to subscribe to the purchaseUpdatedStream event after my app has initialised as I want to access localization text to display messages to the user if a purchase has failed. However I can't get the listen event to fire unless it's subscribed BEFORE the MaterialApp widget is built.
Working example:
class AppConfig extends InheritedWidget {
AppConfig({
#required this.appName,
#required Widget child,
#required this.prefs,
#required this.devMode
}) : super(child: child);
final String appName;
final SharedPreferences prefs;
final bool devMode;
static AppConfig of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var configuredApp = new AppConfig(
appName: 'app',
child: new MyApp(),
prefs: await SharedPreferences.getInstance(),
devMode: true,
);
InAppPurchaseConnection.enablePendingPurchases();
runApp(configuredApp);
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
StreamSubscription<List<PurchaseDetails>> _subscription;
#override
void initState() {
Stream purchaseUpdated =
InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList, context);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
super.initState();
}
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList, BuildContext context) {
var config = AppConfig.of(context);
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
print('pending');
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
print('error');
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
print('purchased');
}
if (purchaseDetails.pendingCompletePurchase) {
print('complete');
await InAppPurchaseConnection.instance
.completePurchase(purchaseDetails);
}
}
});
}
#override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return MultiProvider(
providers: [
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: config.appName,
home: Scaffold(
body: SomeWidget(); // whack a button in this widget that triggers a product purchase
),
)
);
}
}
Non-working example:
class AppConfig extends InheritedWidget {
AppConfig({
#required this.appName,
#required Widget child,
#required this.prefs,
#required this.devMode
}) : super(child: child);
final String appName;
final SharedPreferences prefs;
final bool devMode;
static AppConfig of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var configuredApp = new AppConfig(
appName: 'app',
child: new AppScaffold(),
prefs: await SharedPreferences.getInstance(),
devMode: true,
);
InAppPurchaseConnection.enablePendingPurchases();
runApp(configuredApp);
}
class AppScaffold extends StatelessWidget {
#override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return MultiProvider(
providers: [
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: config.appName,
home: Scaffold(
body: MyApp()
),
)
);
}
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
StreamSubscription<List<PurchaseDetails>> _subscription;
#override
void initState() {
Stream purchaseUpdated =
InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList, context);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
super.initState();
}
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList, BuildContext context) {
var config = AppConfig.of(context);
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
print('pending');
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
print('error');
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
print('purchased');
}
if (purchaseDetails.pendingCompletePurchase) {
print('complete');
await InAppPurchaseConnection.instance
.completePurchase(purchaseDetails);
}
}
});
}
#override
Widget build(BuildContext context) {
return SomeWidget(); // whack a button in this widget that triggers a product purchase
}
}
Can anyone see if I'm going about this the wrong way and/or explain why this doesn't work?
My own fault - I was using Navigator.pushReplacement(...); elsewhere in the app which was triggering the dispose method on the child widget. Obvious now I think about it.