Unhandled Exception: A class was used after being disposed. - Flutter - flutter

I'm getting this error
E/flutter ( 9610): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: A CustRegViewModel was used after being disposed.
E/flutter ( 9610): Once you have called dispose() on a CustRegViewModel, it can no longer be used.
I have a View named CustRegView where I take a phone number form the user and send it to ViewModel named CustRegViewModel to authenticate which returns bool based on its authentication status.
class CustRegView extends StatefulWidget {
#override
_CustRegViewState createState() => _CustRegViewState();
}
class CustRegView extends StatelessWidget{
final TextEditingController _controller = TextEditingController();
#override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
return BaseView<CustRegViewModel>(
builder: (context, model, child) => Scaffold(
...<some code>
FlatButton (
onPressed: () async {
var registerSuccess = await model.register( _controller.text, context);
// ^^^^^ HERE, IN ABOVE LINE, I'M GETTING AN ERROR ^^^^^^
if (registerSuccess) {
Navigator.pushNamed(context, 'newScreen');
} else {
UIHelper().showErrorButtomSheet(context, model.errorMessage);
}
)
}
CustRegViewModel looks like this
class CustRegViewModel extends BaseViewModel {
final AuthService _authService = locator<AuthService>();
final DialogService _dialogService = locator<DialogService>();
dynamic newUserResult;
dynamic verifyResult;
Future<bool> register(String phoneNo, BuildContext context) async {
await verifyPhone;
return verifyResult ? true : false; // From here it returns true
}
Future<void> verifyPhone(phoneNo) async {
await FirebaseAuth.instance.verifyPhoneNumber(
phoneNumber: updatedPhoneNo,
timeout: Duration(seconds: 50),
verificationCompleted: (AuthCredential authCred) async {...... <some code>
verificationFailed: (AuthException authException) {...... <some code>
codeSent: (String verID, [int forceCodeResend]) async {...... <some code>
codeAutoRetrievalTimeout: (String verID) {...
).catchError((error) {...... <some code>
}
}
BaseView looks like this
class BaseView<T extends BaseViewModel> extends StatefulWidget {
final Widget Function(BuildContext context, T model, Widget child) builder;
final Function(T) onModelReady;
BaseView({this.builder, this.onModelReady});
#override
_BaseViewState<T> createState() => _BaseViewState<T>();
}
class _BaseViewState<T extends BaseViewModel> extends State<BaseView<T>> {
T model = locator<T>();
#override
void initState() {
if (widget.onModelReady != null) {
widget.onModelReady(model);
}
super.initState();
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<T>(
create: (context) => model,
child: Consumer<T>(builder: widget.builder),
);
}
}
BaseViewModel looks like this
class BaseViewModel extends ChangeNotifier {
ViewState _state = ViewState.Idle;
ViewState get state => _state;
void setState(ViewState viewState) {
_state = viewState;
notifyListeners();
}
}

You may have used RegisterLazySingleton instead of Factory in your depencie injection.

Related

how to solve A was used after being disposed. Once you have called dispose() on a A, it can no longer be used. with riverpod?

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(

Why Provider is not giving the initialized value instead of expected one?

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

Bad state error when fetching data from firebase

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

Flutter Bloc add event triggers only once for the same widget and won't trigger again

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

in_app_purchase purchaseUpdatedStream listen event not firing

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.