Flutter Change and refresh body content with Provider - flutter

In the Scaffold's body, I have an IndexedStack which contains several pages. My goal is to do like a single page app.
I also have a Drawer for this widget (but in a separate widget). It enables to change the content via updating the index of the IndexedStack. This is possible because I made a ChangeNotifier (from Provider) to notify my body that the index has changed (Consumer).
My solution works but the problem is that IndexedStack loads all the pages at the beginning and won't refresh them later. I've found a trick for that, but I need to call setState() -> I can't do it from my Drawer. So, is there a way to trigger setState() when the index from the ChangeNotifier changes ? Atm I can read its value, but how to know when it is changing ?
Update:
In scaffold's body (with Consumer):
IndexedStack(
index: navigation.pageIndex, // -> from Consumer
children: [
Page0(),
Page1(uid: navigation.uid), // -> from Consumer
]
)
navigation_provider.dart:
class NavigationProvider extends ChangeNotifier {
int _pageIndex = 0;
String _uid = '';
int get pageIndex => _pageIndex;
set pageIndex(int i) {
_pageIndex = i;
notifyListeners();
}
String get uid => _uid;
set uid(String uid) {
_uid = uid;
notifyListeners();
}
}
In my drawer, I do (when I want Page1 to be displayed):
Provider.of<NavigationProvider>(context,listen: false).uid = 'newValue';
Provider.of<NavigationProvider>(context, listen: false).pageIndex = 1;
Result: Page1() is always called with uid = '' (no update)

Related

Riverpod Flutter StateError (Bad state: called ProviderSubscription.read on a subscription that was closed)

I'm trying to update my state management solution to Riverpod and I'm all over the place. I get this error when I try to pop a page from my nav stack. I have a provider to save a DocumentSnapshot from firestore so I can call certain fields from it. This is probably where the problem is coming from:
final docIdProvider = StateProvider<DocumentSnapshot?>((ref) => null);
final docProvider = Provider((ref) {
final docId = ref.watch(docIdProvider);
return docId;
});
My home page I have a list of trips, the flow goes like this. The issue is when I try to pop from details to home page, or from join to details.
When a user clicks on a trip from the homepage, I update the provider:
return ListView(
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return Card(
child: ListTile(
title: Text('From ${data['departure']}'),
),
onTap: () {
ref.read(docIdProvider.notifier).state = document;
I have a google maps page and other UI where I use the provider to call the fields of the document like so:
if ((ref.watch(docProvider)!['departure_lat']) >
ref.watch(docProvider)!['arrival_lat']
This is where it goes wrong when I try to pop the stack. the
if ((ref.watch(docProvider)!['departure_lat']) >
Gets highlights and displays the
StateError (Bad state: called ProviderSubscription.read on a subscription that was closed)
code.
I tried creating another riverpod provider as such:
class DocumentSnapshotNotifier extends StateNotifier<DocumentSnapshot?> {
DocumentSnapshotNotifier() : super(null);
DocumentSnapshot? get documentSnapshot => state;
set documentSnapshot(DocumentSnapshot? documentSnapshot) {
state = documentSnapshot;
}
void updateDocumentSnapshot(DocumentSnapshot? documentSnapshot) {
state = documentSnapshot;
}
void clearDocumentSnapshot() {
state = null;
}
}
final documentProvider =
StateNotifierProvider<DocumentSnapshotNotifier, DocumentSnapshot?>(
(ref) => DocumentSnapshotNotifier());
But I was getting a different error about the widget tree being unstable. Still working out this riverpod solution.

How to change the default routing behavior when entering a new URL through address bar in a flutter web app? (using getx)

I am working on a Flutter Web App using Getx for navigation and state management. One of the routes in my flutter app has two query parameters. Let us call these parameters Dataset and Index. When the Dataset parameter is changed through the URL, I want to make an API call to retrieve the new dataset, and when the Index parameter is changed, I want to display the data from the dataset at that particular index on the app. Index in this case is an observable RxInt variable defined in the controller.
However, the default behavior when I change the URL and press enter is for the Flutter app to push a new page on to the navigation stack. The behavior I prefer is to simply update the values and make a new API call if necessary. The API call may be done by simply refreshing the page since it is handled by the Getx controller onInit function.
I'm not very familiar with how routing in flutter works and I haven't found a solution to change the behavior for routing itself. I've tried a few ways to update the values despite pushing the new page on to the stack, such as setting the value for index through the initState or build calls on my widgets but those changes aren't visible on my UI. I've also tried reinitializing the controller by deleting it but that didn't work either.
EDIT: I have added a code example:
Widget:
class MainscreenView extends StatefulWidget {
const MainscreenView({Key? key}) : super(key: key);
#override
State<MainscreenView> createState() => _MainscreenViewState();
}
class _MainscreenViewState extends State<MainscreenView> {
late MainscreenController mainscreenController;
#override
Widget build(BuildContext context) {
return GetX<MainscreenController>(
init: MainscreenController(),
initState: (_) {
mainscreenController = Get.find<MainscreenController>();
},
builder: (_) {
return Scaffold(
body: Center(
child: Text(
'Current index is ${mainscreenController.index.value}',
style: const TextStyle(fontSize: 20),
),
),
);
});
}
}
Controller:
class MainscreenController extends GetxController {
final index = 0.obs;
late String? dataset;
#override
void onInit() {
super.onInit();
final String? datasetQuery = Get.parameters['dataset'];
if (datasetQuery != null) {
dataset = datasetQuery; //API call goes here
} else {
throw Exception('Dataset is null');
}
final String? indexString = Get.parameters['index'];
if (indexString == null) {
throw Exception('Index is null');
} else {
final int? indexParsed = int.tryParse(indexString);
if (indexParsed == null) {
throw Exception('Index Cannot be parsed');
} else {
index.value = indexParsed;
}
}
}
}
The initial route is /mainscreen?dataset=datasetA&index=0. If I were to modify the route in the address bar to /mainscreen?dataset=datasetA&index=5 for example and press enter, The current behavior of Flutter is to push a new page onto the navigation stack. I would like to update the value of index instead and display it on the same page, but I haven't found a way to accomplish this. Also, if dataset parameter is updated I would like to again avoid pushing a new page onto the stack and refresh the current page instead so that the onInit function is run again and the API call is made automatically.

In Flutter, navigating to a page that have a heavy widget is too slow in an old device environmnet

I'd like to implement Survey Platform
A survey has many questions and each of them has a part (ex. Part1, Part2, and so on...)
Each part is displayed in one page. (One page equals to one page)
After user finished the survey, they could check the result in one page. That page has a SingleChildScrollView and the SingleChildScrollView contains all of the question's answers. (This is a client's request, so it could not be revised)
Also, I have selected GetX library to administrate State and used RxDart to manage async.
At the onInit method in a answers page's controller, I call the result api and save it to RxList variable.
In answers page's view, if the RxList variable's value is null, it builds CircularProgressIndicator. And in a case of not null, it builds SingleChildScrollView.
I'd like to make the answers page right away pop up when the Get.toNamed method is called and the CircularProgressIndicator has been displayed until the RxList variable is initialized.
But, when I called the Get.toNamed method, the screen had been stoped in the page that called Get.toNamed method. And a few seconds later, the answers page finally pop up.
What should I do to solve this problem?? My code is like belows.
// Survey Answers View
class SurveyResultDetailView extends BaseView<SurveyResultDetailController> {
#override
PreferredSizeWidget? appBar(BuildContext context) {
#override
Widget body(BuildContext context) {
return Obx(
() {
if (controller.surveyDiagnosisUiModel == null) {
return CircularProgressIndicator();
}
return SingleChildScrollView(
child: Column(
children: [
So long childrens~~~
],
),
);
}
);
}
}
// Survey Answers Controller
class SurveyResultDetailController extends BaseController {
final PreferenceManager _preferenceManager =
Get.find(tag: (PreferenceManager).toString());
late final String surveyConfigId;
final Rxn<SurveyDiagnosisResultUiModel> _rxModel =
Rxn<SurveyDiagnosisResultUiModel>(null);
SurveyDiagnosisResultUiModel? get surveyDiagnosisUiModel => _rxModel.value;
void setSurveyDiagnosisUiModel(SurveyDiagnosisResultUiModel? value) {
_rxModel.value = value;
}
final RxList<SurveyQuestionListUiModel> _surveyQuestionListUiModel =
RxList<SurveyQuestionListUiModel>();
List<SurveyQuestionListUiModel> get surveyQuestionListUiModel =>
_surveyQuestionListUiModel.toList();
void getDiagnosisResultUiModel() {
Future<String> diagnosisResultFuture =
_preferenceManager.getString('recent_detail_result');
callDataService(
diagnosisResultFuture,
onSuccess: _handleDiagnosisResultResponseSuccess,
);
}
void _handleDiagnosisResultResponseSuccess(String response) {
~~~~~ response entity mapping to ui model code
_surveyQuestionListUiModel.refresh();
}
List<SurveyQuestionListUiModel> parseQuestionListByPartNumber(int number) {
return surveyQuestionListUiModel
.where((element) => element.partNumber == number)
.toList();
}
/// ------------> 생애설계 행동 진단 결과 관련 값
#override
void onInit() {
surveyConfigId = Get.arguments as String;
getDiagnosisResultUiModel();
super.onInit();
}
}

In Getx Flutter how can I get result from function?

I would like to get a percent making two functions.
My first function is a filtered list of my data.
int countP(String idC) {
final _ps = ps.where((element) => element.idC == idC);
return _ps.length;}
My second function comes from my first function:
int countPP(String idC) {
final _ps = ps.where((element) => element.idC == idC);
final _psPd = _ps.where((element) => element.pd != '');
return _psPd.length;}
In my view I would like to show the percent:
final percentPd =((pCtrl.countPP(idC) * 100) / pCtrl.countP(idC)).round();
I need to show the result in Text:
Text(percentPd)
My question is:
How can I show the result in Text Widget using Getx, because when I open my view the first time doesn't show the result, but if I refresh, yes?
I used Obx, GetX, and GetBuilder in my Text.
I put my controller using Get.find() but doesn't work.
I Used Get.put(Controller) and doesn't work
You should declare percentPd like this
final percentPd = 0.obs;
percentPd.value = ((pCtrl.countPP(idC) * 100) / pCtrl.countP(idC)).round();
and then assign it's value afterwards and use Obx on the widget where you want to show it.
In your widget use this code :
#override
Widget build(BuildContext context) {
return {
GetBuilder<yourController>(builder: (controller)
{
return Text(controller.percentPd)
}
}

How to display a dialog from ChangeNotifier model

In my project, when ChangeNotifier class receives a status it sets up a boolean and calls notifyListeners(). In my main build() function, I then check the pending-boolean and display the dialog accordingly - but I am only able to display the dialog by giving it a small delay in the build method - it seems the context is missing.
TL;DR:
Is there any way to display a dialog from within the ChangeNotifier class?
Even if you could do that by just passing a BuildContext, you shouldn't, because you'd be coupling your ChangeNotifier to specific cases only.
Let's say this is your model:
class Model extends ChangeNotifier {
bool _loading = false;
bool get loading => _loading;
void update(bool value) {
_loading = value;
notifyListeners();
}
}
And say, you're updating loading value on a button press using:
final model = Provider.of<Model>(context, listen: false);
model.update(true);
You should perform your logic here itself, or maybe you are listening to this model somewhere else in your project with:
final model = Provider.of<Model>(context);
You then should show dialog by checking:
if (model.loading) {
// show dialog
}