Flutter: widget has been unmounted issue during dismissible actions - android

i have an issue with the widget unmounted with dismissdirection action on flutter. When I left swipe the dismissible item with the deleted action confirmed, the error occured as following:
The following assertion was thrown while notifying status listeners for AnimationController:
This widget has been unmounted, so the State no longer has a context (and should be considered
defunct).
Consider canceling any active work during "dispose" or using the "mounted" getter to determine if
the State is still active.
The full error codes are here
My code:
home_page.dart. The homepage I use statefulwidget and redirect to ExpensesCategoryHistory() Screen.
class HomePage extends StatefulWidget {
const HomePage({Key key, this.database, this.budget}) : super(key: key);
final DatabaseService database;
final Budget budget;
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
final database = Provider.of<DatabaseService>(context, listen: false);
PersistentTabController _controller;
_controller = PersistentTabController(initialIndex: 0);
return PersistentTabView(
context,
controller: _controller,
screens: _buildScreens(),
items: _navBarsItems(),
confineInSafeArea: true,
backgroundColor: Colors.white,
handleAndroidBackButtonPress: true,
resizeToAvoidBottomInset: true,
stateManagement: true,
hideNavigationBarWhenKeyboardShows: true, // Recommended to set 'resizeToAvoidBottomInset' as true while using this argument. Default is true.
decoration: NavBarDecoration(
borderRadius: BorderRadius.circular(10.0),
colorBehindNavBar: Colors.white,
),
popAllScreensOnTapOfSelectedTab: true,
popActionScreens: PopActionScreensType.all,
itemAnimationProperties: ItemAnimationProperties( // Navigation Bar's items animation properties.
duration: Duration(milliseconds: 200),
curve: Curves.ease,
),
screenTransitionAnimation: ScreenTransitionAnimation( // Screen transition animation on change of selected tab.
animateTabTransition: true,
curve: Curves.ease,
duration: Duration(milliseconds: 200),
),
navBarStyle: NavBarStyle.style15, // Choose the nav bar style with this property.
);
}
}
List<Widget> _buildScreens() {
return [
Home(),
ExpensesCategoryHistory(),
BudgetPage(),
Container(),
Container()
];
}
Then, in the ExpensesCategoryHistory() class. So, user can select the category and prompt expenses based on the category (when the list item is tapped.) Refer Steps 4 of error occured with images
class ExpensesCategoryHistory extends StatefulWidget {
#override
_ExpensesCategoryHistoryState createState() => _ExpensesCategoryHistoryState();
}
class _ExpensesCategoryHistoryState extends State<ExpensesCategoryHistory> {
var categoryList = ["Beauty", "Entertainment", "Food & Drinks", "Groceries", "Medical", "Transport", "Others"];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: kPrimaryColor,
title: Text('Expenses History By Category'),
),
body: _buildContents(context),
floatingActionButton: FloatingActionButton(
backgroundColor: kPrimaryColor,
child: Icon(Icons.add),
onPressed: () => EditExpensesPage.show(context,
database: Provider.of<DatabaseService>(context, listen: false),
),
),
);
}
Widget _buildContents(BuildContext context) {
final database = Provider.of<DatabaseService>(context, listen: false);
return ListView.builder(
itemCount: categoryList.length,
itemBuilder: (context, index){
return ListTile(
title: Text('${categoryList[index]}'),
onTap: () {
if(mounted){
Navigator.push(
context,
MaterialPageRoute<Widget>(
builder: (context) => ExpensesHistory(Category: categoryList[index]),
),
);
}
},
);
},
);
}
}
Then, lasty the main issue is here. When user select on delete action after left swipe of item. The error code occurred and did not perform Navigator.of(context).pop(true)
ExpensesHistory class
class ExpensesHistory extends StatelessWidget {
// Declare a field that holds the Todo.
final String Category;
// In the constructor, require a Todo.
ExpensesHistory({Key key, #required this.Category}) : super(key: key);
Future<void> _delete(BuildContext context, Expense expense) async {
try {
final database = Provider.of<DatabaseService>(context, listen: false);
await database.deleteExpenses(expense);
} on PlatformException catch (e) { //handle error
PlatformExceptionAlertDialog(
title: 'Operation failed',
exception: e,
).show(context);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: kPrimaryColor,
title: Text('$Category'),
),
body: _buildContents(context),
floatingActionButton: FloatingActionButton(
backgroundColor: kPrimaryColor,
child: Icon(Icons.add),
onPressed: () => EditExpensesPage.show(context,
database: Provider.of<DatabaseService>(context, listen: false),
),
),
);
}
Widget _buildContents(BuildContext context) {
final database = Provider.of<DatabaseService>(context, listen: false);
List myExpenses = [];
return StreamBuilder<List<Expense>>(
stream: database.expensesStream(),
builder: (context, snapshot) {
return ListItemsBuilder<Expense>(
snapshot: snapshot,
itemBuilder: (context, expense) =>
Dismissible(
confirmDismiss: (DismissDirection direction) async {
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Delete Confirmation"),
content: const Text(
"Are you sure you want to delete this item?"),
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text("Delete"),
),
FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text("Cancel"),
),
],
);
},
);
},
key: UniqueKey(),
background: slideRightBackground(),
direction: DismissDirection.endToStart,
onDismissed: (direction) => _delete(context, expense),
child: ExpensesListTile(
expense: expense,
onTap: () => ExpensesCategoryPage.show(context, expense),
category: Category,
),
),
);
},
);
}
}
Steps of error occured with images
To ExpensesCategoryHistory class, render page here
Select category Beauty here
Render Beauty category item here
Confirm Delete action on category item after left swipe of dismissible here
Selected delete, prompt error code + item not deleted
Return stay step 3
Sorry for the full codes, but I wonder if my implementation issue on flutter/dart? I have been solving for days but still same issue, even I've read couple related issues on stackoverflow. Any help is greatly appreciated !
Tested
All widgets are mounted within 3 classes after routing.
Error occurred when deleted action is tapped but widget is mounted still.

Related

Sending data to other page with bloc cubit

My problem is this. I created cubit for 2 different pages. When I am on the first page, I can fill the list inside the 2nd page and I can read it from the log. However, when I go to the second page, the list I filled in from the previous page is still empty.
Main.dart
home:
MultiBlocProvider(
providers: [
BlocProvider(create: (_) => HomeCubit(PhotoService())),
BlocProvider(create: (_)=> FavoritesCubit())//Gerekiyor,homeviewda içerisindeki methoda erişmem gerekiyor
],
child: const HomeView())
HomeView.dart
where I run the function in favoritescubit and add it to the list
onTap: () {
BlocProvider.of<FavoritesCubit>(context).addFavorite(
context,
state.selectItem![index],
);
print(state.selectItem?[index].isSelected);
context.read<FavoritesCubit>().getAllFavorites();
// print("UI --- ${state.selectItem![index].isSelected}");
// context.read<FavoriteBloc>().add(
// AddFavorite(photoList, photoList.isSelected));
// print(" ispressed ${photoList.isSelected}");
},
FavoritesCubit.dart
class FavoritesCubit extends Cubit<FavoritesState> {
FavoritesCubit() : super(const FavoritesState());
final List<PhotoModel> favoriteList = <PhotoModel>[];
Future<void> getAllFavorites() async {
print("FavoriteList : ${favoriteList.length}");
emit(state.copyWith(favoriteList: favoriteList));
}
Future<void> addFavorite(
BuildContext context,
PhotoModel photo,
) async {
photo.isSelected = !photo.isSelected;
if (favoriteList.contains(photo) == false) {
favoriteList.add(photo);
emit(state.copyWith(
favoriteList: favoriteList, isFavorite: photo.isSelected));
print("${state.favoriteList!.length}asdasd");
} else if (favoriteList.contains(photo) == true) {
favoriteList.remove(photo);
emit(state.copyWith(
favoriteList: favoriteList, isFavorite: photo.isSelected));
print("${state.favoriteList!.length}asdasd");
}
FavoriteView.dart
class FavoriteView extends StatefulWidget {
const FavoriteView({Key? key}) : super(key: key);
#override
State<FavoriteView> createState() => _FavoriteViewState();
}
class _FavoriteViewState extends State<FavoriteView> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => FavoritesCubit()..getAllFavorites(),
child: Scaffold(
appBar: AppBar(
title: const Text("Bloc Example"),
),
body: buildFavoriteList(context),
),
);
}
}
Widget buildFavoriteList(BuildContext context) {
return BlocConsumer<FavoritesCubit, FavoritesState>(
listener: (context, state) {
// TODO: implement listener
},
builder: (context, state) {
return ListView.builder(
itemCount: state.favoriteList?.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: (() {
// navigateToPostDetailPage(context, photos[index]);
}),
child: Padding(
padding: const EdgeInsets.all(10),
child: PhotoListTile(
isPressed: state.favoriteList![index].isSelected,
imageUrl: state.favoriteList![index].thumbnailUrl.toString(),
title: state.favoriteList![index].title.toString(),
url: state.favoriteList![index].url.toString(),
onTap: () {
// context.read<HomeCubit>().addFavorite(
// context,
// state.favoriteList![index],
// state.favoriteList![index].isSelected);
// context
// .read<FavoriteBloc>()
// .add(RemoveFavorite(photos[index]));
},
),
),
);
});
},
);
Okey, now with your added information about your FavoriteView it is clear what the problem is.
In your FavoriteView you create a new cubit, which is not the same as you created in the MultiBlocProvider. That is why it is always empty on your FavoriteView
create: (context) => FavoritesCubit()..getAllFavorites(), // This is the issue
Make sure that your FavoriteView is a child somewhere under your MultiBlocProvider and remove the creation of a new FavoritesCubit in that view. I.e. remove the BlocProvider in your FavoriteView

Flutter: cubit to cubit communication

I have a finance app that has wallets ( accounts ) and transactions per wallet.
Now I need to switch between wallets and change all transactions components underneath.
What currently I am doing, creating a cubit called activeWalletCubit and I just listen to it if it changed, and reinitiate transactions components based on that wallet, like the showing on below code.
I need to know if what I am doing is right or best practice, or is there a better way to do it?!
class TransactionsList extends StatelessWidget {
const TransactionsList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<TransactionWatcherCubit>()
..initialized(
BlocProvider.of<ActiveWalletCubit>(context).state.walletDomain),
child: BlocListener<ActiveWalletCubit, ActiveWalletState>(
listenWhen: (previous, current) =>
previous.walletDomain != current.walletDomain,
listener: (context, state) {
BlocProvider.of<TransactionWatcherCubit>(context)
.initialized(state.walletDomain);
},
child: BlocBuilder<TransactionWatcherCubit, TransactionWatcherState>(
builder: (context, state) {
return state.when(
initial: () => Container(),
loading: () => const Center(child: CircularProgressIndicator()),
error: (_) => const Center(child: Text('error')),
loaded: (transactions) => ListView.builder(
itemCount: transactions.length,
itemBuilder: (context, i) => ListTile(
title: Text(
's ${transactions[i].id} ${transactions[i].value} ${transactions[i].date}',
),
),
),
);
},
),
),
);
}
}

Error trying to read items from a list of strings to show in a ListView

In the code below I am trying to build a basic ToDo list app using flutter. I have a FAB and when it is pressed, it asks the user to enter a text in the popped up alert dialog that contains a TextField. I also use a TextEditingController to get the text and add it to a list of strings.
I have a counter variable to keep track of items being added to the list and to use it as index of the list when I want to show the item from the list and add it to the ListView as a ListTile.
When I run the code it says the index is out of range and I really don't know what else should I take care of. Sorry if my question is basic, I am newbie.
My Code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ToDo List',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyTaskList(),
);
}
}
class MyTaskList extends StatefulWidget {
#override
_MyTaskListState createState() => _MyTaskListState();
}
class _MyTaskListState extends State<MyTaskList> {
final _taskItems = <String>[];
var _counter = 0;
final myController = TextEditingController();
#override
void dispose(){
myController.dispose();
super.dispose();
}
void _addTask(String task){
setState(() {
_taskItems.add(task);
});
myController.clear();
}
Widget _buildTaskList() {
return new ListView.builder(
itemCount: _taskItems.length,
itemBuilder: (BuildContext context, _) {
print(_taskItems);
return _buildTask(_taskItems[_counter]);
}
);
}
Widget _buildTask(String task) {
return new ListTile(
title: new Text(task),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ToDo List"),
),
floatingActionButton: FloatingActionButton(
onPressed: () => showDialog<String>(
context: context,
builder: (context) => AlertDialog(
title: Text("New Task"),
content: TextField(
controller: myController,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: "Enter New Task",
),
),
actions: <Widget>[
TextButton(
onPressed: () => {
_addTask(myController.text),
Navigator.pop(context, "ok"),
_counter++,
print(_counter),
print(_taskItems),
},
child: const Text("OK")),
],
)
),
child: Center(child:Icon(Icons.add)),
),
body: _buildTaskList(),
);
}
}
Edit as below. You can use the ListViewBuilder index, why do you use counter? I think, your initial counter value is 0 but the list is empty. You try to get element 0 (or first)` of empty list, but there is no element.
Widget _buildTaskList() {
return new ListView.builder(
itemCount: _taskItems.length,
itemBuilder: (BuildContext context, index) {
print(_taskItems);
return _buildTask(_taskItems[index]);
}
);
}

Flutter : How to store 'Favorited List' using shared_preference

This problem is about how to keep the favorited item in the List, even after I re-open the app.
I want to keep the favorited item in doaList into favDoa, even after I close my app and re-open it. I've seen about the shared_preference package in flutter to store data, but i confused how can i implement it into my app. Here is my code :
import 'package:flutter/material.dart';
import 'package:json_test/class/doa.dart';
import 'package:json_test/page/DoaPage.dart';
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
List<Doa> doaList;
List<Doa> favDoa;
bool _isInit = true;
Future<void> fetchDoa(BuildContext context) async {
final jsonstring =
await DefaultAssetBundle.of(context).loadString('assets/doa.json');
doaList = doaFromJson(jsonstring);
_isInit = false;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("JSON Data test"),
),
body: Container(
child: FutureBuilder(
future: _isInit ? fetchDoa(context) : Future(null),
builder: (context, _) {
if (doaList.isNotEmpty) {
return ListView.builder(
itemCount: doaList.length,
itemBuilder: (BuildContext context, int index) {
Doa doa = doaList[index];
return Card(
margin: EdgeInsets.all(8),
child: ListTile(
title: Text(doa.judul),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
DoaPage(
doa: doa,
)));
},
trailing: IconButton(
icon: Icon(
doa.fav
? Icons.favorite
: Icons.favorite_border,
color: doa.fav ? Colors.red : null,
),
onPressed: () => setState(() {
doa.fav = !doa.fav;
}),
)));
},
);
}
return CircularProgressIndicator();
})));
}
}
when I click the favorite icon in the list, it will be marked true in the "doa.fav". How can I implement the shared_preference package in my code to keep the doa.fav's data? Thank you so much for your answer :)
Try this:
Create a list of integer to store the Doa Ids, say you name it favoriteList
Each time you click the favorite button, add the Doa's id to favoriteList. Also save it to shared_preferences. It only support list of string, so you need to convert it first, something like:
List<String> stringFavoriteIds =
favoriteList.map((e) => e.toString()).toList();
SharedPrefs().favoriteIds = stringFavoriteIds ;
Next, each time you open the app, load SharedPrefs().favoriteIds to favoriteList
Compare the Doa Ids in favoriteList to your list of doa to mark Doa.fav to true for matching Ids.

Flutter : Provider do not update data in the previous screen

In my Flutter application I am using Provider version 4.0.4 to manage the state of my app. In basic terms, my app will list down the nearby companies with their rating. users can select a organisation, open it and add their rating as well, so the final rating will be updated. I am using the Consumer concept in Provider to handle the tasks.
In NearByPlacesPage class I am listing down the companies around me with rating information. User can click on a company and they will be taken to OrganizationPage page.
In OrganizationPage class, the rating is displayed again. user can add their rating to the system. Then the rating information in both OrganizationPage page and NearByPlacesPage (back page) need to be updated.
The issue is, when the user update the rating, the rating in OrganizationPage get updated but not NearByPlacesPage in back stack. When we go back to NearByPlacesPage, we can clearly see the old rating values. The page need to be reloaded to get updated values.
Below are the important sections in my code
NearByPlacesPage
class NearByPlacesPage extends StatelessWidget {
int orgTypeID;
String orgTypeName;
NearByPlacesPage(this.orgTypeID, this.orgTypeName);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => RatingService()),
],
child: SingleChildScrollView(
child: _NearByPlacesPageUI(orgTypeID, orgTypeName),
),
),
appBar: AppBar(
title: Text(orgTypeName),
),
);
}
}
class _NearByPlacesPageUI extends StatefulWidget {
int orgTypeID;
String orgTypename;
_NearByPlacesPageUI(this.orgTypeID, this.orgTypename);
#override
State<StatefulWidget> createState() {
return _NearByPlacesPageState();
}
}
class _NearByPlacesPageState extends State<_NearByPlacesPageUI> {
#override
Widget build(BuildContext context) {
Consumer<RatingService>(builder: (context, data, child){
return Flexible(
child: ListView.builder(
itemCount: orgList.length,
itemBuilder:(BuildContext context, int index) {
Organization organization = orgList[index];
if (organization.isDisabled != true) {
RatingValue ratingValue = data.getData();
return Container(
margin: EdgeInsets.only(
top: 5, left: 5, right: 5),
child: _buildPlace(organization, ratingValue));
} else {
return Container();
}
},),
);
},);
}
}
OrganizationPage
class OrganizationPage extends StatelessWidget {
Organization organization;
String orgTypeName;
OrganizationPage(this.organization, this.orgTypeName);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: _OrganizationPageUI(organization, orgTypeName),
),
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(organization.name),
),
);
}
}
class _OrganizationPageUI extends StatefulWidget {
Organization organization;
String orgTypeName;
_OrganizationPageUI(this.organization, this.orgTypeName);
#override
State<StatefulWidget> createState() {
return _OrganizationPageState();
}
}
class _OrganizationPageState extends State<_OrganizationPageUI> {
#override
Widget build(BuildContext context) {
Consumer<RatingService>(
builder: (context, data, child) {
Consumer<RatingService>(
return Row(
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 10, left: 10),
child: Text(daa.getData()
style: Theme.of(context).textTheme.bodyText2.apply(color: Colors.grey),
),
),
],
);
),
}
}
}
In OrganizationPage there is a AlerDialog, which allows the user to rate and save. When saved, it will call another method which will reload the data.
Widget _ratingDialog(double _rating) {
RatingService _ratingService =
Provider.of<RatingService>(context, listen: false);
Rating _rating = _ratingService.returnRating();
double _ratingValue = _ratingService.returnRating().rating;
return AlertDialog(
title: const Text("Your Rating"),
actions: [
new FlatButton(
child: const Text("Save"),
//onPressed: () => Navigator.pop(context),
onPressed: () async {
Rating rating = Rating(
idrating:
_rating.idrating != null ? _rating.idrating : null,
user: _user,
organization: widget.organization,
rating: _ratingValue,
dateCreated: DateTime.now().millisecondsSinceEpoch,
lastUpdated: DateTime.now().millisecondsSinceEpoch);
await _ratingService.saveOrUpdateRating(rating, authToken);
_loadRatingByUserAndOrganization(authToken);
_loadRatingValueByOrganization(authToken);
Navigator.pop(context);
},
),
],
);
}
Future _loadRatingByUserAndOrganization(String authToken) {
RatingService _ratingService =Provider.of<RatingService>(context, listen: false);
return _ratingService.getRatingByUserAndOrganization(
_authService.getDatabaseUser().user.iduser,
widget.organization.idorganization,
authToken);
}
RatingService
This is the class which is responsible for calling notifyListeners(). It will be triggered by the above AlertDialog and the expected behaviour is to reload data in both OrganizationPage and NearByPlacesPage
class RatingService with ChangeNotifier {
List<RatingValue> _ratingValueList ;
List<RatingValue> getData()
{
return _ratingValueList;
}
//Load rating by user and Organization
Future<void> getRatingByUserAndOrganization(int idUser, int organizationID, String authToken) async {
try {
var data = await http.get(
_navLinks.getRatingByUserAndOrganization(idUser, organizationID),
headers: {HttpHeaders.authorizationHeader: "Bearer $authToken"},
);
print(data.body);
_rating = Rating.fromJson(convert.json.decode(data.body));
notifyListeners();
} catch (error) {
print(error);
throw error;
}
}
}
What I have I done wrong?

Categories

Resources