Storing data in class level global variables - flutter

I have home.dart class which on build, fetchs an api result and assigns the response as well as the fetched time (DateTime.now()).
here is the StatefulWidget Home class
class HomeView extends StatefulWidget {
int maxCacheDurationInMinutes = 2;
DateTime _lastUpdatedTime;
Usage previousFetchedUsage;
#override
_HomeView createState() => _HomeView();
}
on the state, i access the elements using widget._lastUpdatedTime like so.
here is the state class and the requesting function
class _HomeView extends State<HomeView> {
bool get isValidCache =>
widget._lastUpdatedTime != null &&
DateTime.now().difference(widget._lastUpdatedTime).inMinutes <
widget.maxCacheDurationInMinutes;
int get minsSinceLastCached => widget._lastUpdatedTime != null
? DateTime.now().difference(widget._lastUpdatedTime).inMinutes
: 0;
Future<Usage> requestUsage(BuildContext context) async {
if (isValidCache) {
// is valid cache
if (widget.previousFetchedUsage != null) {
// return cached if it isn't null
return widget.previousFetchedUsage;
}
}
// if cache is invalid or cache is null
// fetch the latest data and assign to cache variable
widget._lastUpdatedTime = DateTime.now();
return widget.previousFetchedUsage = await ClientHandler.of(context).client.fetchUsage();
}
}
// some code is omitted to keep it brief
The problem here is, on each UI rebuild (during hot reload), the request is send. where technically it only should be send once during the 2 minutes period.
I also tried with make previousFetchedUsage as a static variable. still nothing changes.
Is there any concepts i should be aware about in dart ? i am from C# background and recently started with flutter.

your requestUsage is future function and when you use it with FutureBuilder in your ui its never refresh the ui event you fire it in every two minutes.
you can refresh your ui for read the new value with SetState or using Streams in your UI not just FutureBuilder

Related

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.

how can I get the other controller's variable inside one controller in flutter using getx

This is an issue related to the getx in flutter.
I have 2 controllers. ContractsController and NotificationController.
In ContractsController I have put the value into observer variable by calling the Api request.
What I want now is to get that variable's data in another controller - NotificationController.
How to get that value using getx functions?
ContractsController
class ContractsController extends GetxController {
ExpiringContractRepository _expiringContractRepository;
final expiringContracts = <ExpiringContract>[].obs; // This is the value what I want in another controller
ContractsController() {
_expiringContractRepository = new ExpiringContractRepository();
}
#override
Future<void> onInit() async {
await refreshContracts();
super.onInit();
}
Future refreshContracts({bool showMessage}) async {
await getExpiringContracts();
if (showMessage == true) {
Get.showSnackbar(Ui.SuccessSnackBar(message: "List of expiring contracts refreshed successfully".tr));
}
}
Future getExpiringContracts() async {
try {
expiringContracts.value = await _expiringContractRepository.getAll(); // put the value from the api
} catch (e) {
Get.showSnackbar(Ui.ErrorSnackBar(message: e.toString()));
}
}
}
The expiringContracts is updated successfully with data after the api request.
Now, I want to get that value in NotificationController
NotificationController
class NotificationsController extends GetxController {
final notifications = <Notification>[].obs;
ContractsController contractsController;
NotificationsController() {
}
#override
void onInit() async {
contractsController = Get.find<ContractsController>();
print(contractsController.expiringContracts); // This shows an empty list ?????
super.onInit();
}
}
Overview
A couple solutions come to mind:
pass the expiringContracts list as a constructor argument to NotificationsController if you only need this done once at instantiation, or
use a GetX worker to update NotificationsController every time expiringContracts is updated
The first solution isn't related to GetX, rather it's just async coordination between ContractsController and NotificationsController, so lets focus on the 2nd solution: GetX Workers.
Details
In NotificationsController, create a method that will receive expiringContracts.
Something like:
class NotificationsController extends GetxController {
void refreshContracts(List<ExpiringContract> contracts) {
// do something
}
}
Please note: none of this code is tested. I'm writing this purely in StackOverflow, so consider this pseudo-code.
In ContractsController we'll supply the above callback method as a constructor arg:
In ContractsController, something like:
class ContractsController {
final expiringContracts = <ExpiringContract>[].obs
final Function(List<ExpiringContract>) refreshContractsCallback;
ContractsController(this.refreshContractsCallback);
#override
void onInit() {
super.onInit();
refreshContracts(); // do your stuff after super.onInit
ever(expiringContracts, refreshContractsCallback);
// ↑ contracts → refreshContractsCallback(contracts)
// when expiringContracts updates, run callback with them
}
}
Here the GetX ever worker takes the observable as first argument, and a function as 2nd argument. That function must take an argument of type that matches the observed variable, i.e. List<ExpiringContract>, hence the Type of refreshContractsCallback was defined as Function(List<ExpiringContract>).
Now whenever the observable expiringContracts is updated in ContractsController, refreshContractsCallback(contracts) will be called, which supplies the list of expiring contracts to NotificationsController via refreshContracts.
Finally, when instantiating the two controllers inside the build() method of your route/page:
NotificationsController nx = Get.put(NotificationsController());
ContractsController cx = Get.put(ContractsController(nx.refreshContracts));
Timeline of Events
NotificationsController gets created as nx.
nx.onInit() runs, slow call of refreshContracts() starts
ContractsController gets created, with nx.refreshContracts callback
your page paints
nx has no contracts data at this point, so you'll prob. need a FutureBuilder or an Obx/ GetX + StatelessWidget that'll rebuild when data eventually arrives
when refreshContracts() finishes, ever worker runs, sending contracts to nx
nx.refreshContracts(contracts) is run, doing something with contracts
Notes
async/await was removed from nx.onInit
ever worker will run when refreshContract finishes
There were some powerful approaches in GetX. I solved this issue with Get.put and Get.find
Here is the code that I added.
ContractsController
class ContractsController extends GetxController {
ExpiringContractRepository _expiringContractRepository;
final expiringContracts = <ExpiringContract>[].obs; // This is the value what I want in another controller
ContractsController() {
_expiringContractRepository = new ExpiringContractRepository();
}
#override
Future<void> onInit() async {
await refreshContracts();
super.onInit();
}
Future refreshContracts({bool showMessage}) async {
await getExpiringContracts();
if (showMessage == true) {
Get.showSnackbar(Ui.SuccessSnackBar(message: "List of expiring contracts refreshed successfully".tr));
}
}
Future getExpiringContracts() async {
try {
expiringContracts.value = await _expiringContractRepository.getAll(); // put the value from the API
// ******************************** //
Get.put(ContractsController()); // Added here
} catch (e) {
Get.showSnackbar(Ui.ErrorSnackBar(message: e.toString()));
}
}
}
NotificationController
class NotificationsController extends GetxController {
final notifications = <Notification>[].obs;
ContractsController contractsController;
NotificationsController() {
}
#override
void onInit() async {
// ******************************** //
contractsController = Get.find<ContractsController>(); // Added here.
print(contractsController.expiringContracts); // This shows the updated value
super.onInit();
}
}
Finally, I have found that GetX is simple but powerful for state management in flutter.
Thanks.

Flutter: Async function in Getx Controller takes no effect when initialized

Updates:
2021/06/11 After hours of debugging yesterday, I confirmed that the problem is caused by aws amplify configuration: _configureAmplify(). Because the location of the amplify server was set wrong, so _configureAmplify() takes several seconds to work... and therefore, the readPost() function did not work on initialization, as it must run after _configureAmplify()...
2021/06/10I made changes to my code according to S. M. JAHANGIR's advice, and updated the question. The issue still presists. The value of posts is not updated when called in initialization and the data only shows up after reload. (if I commented out the _controller.readPost() in UI, the value of posts is always empty.
I have this page that loads information from aws amplify with getx implemented. However, I found out the readPost() async funtion in getx controller dart file is not reading from database, when the controller instance is initialized. I have to add a _controller.readPost() in UI file to make it work. And the data only shows up after a reload of that UI page...
Getx Controller dart file:
class ReadPostController extends GetxController {
var isLoading = true.obs;
var posts = <Posty>[].obs;
#override
void onInit() {
_configureAmplify();
await readPost();
super.onInit();
// print('show post return value: $posts');
}
void _configureAmplify() {
final provider = ModelProvider();
final dataStorePlugin = AmplifyDataStore(modelProvider: provider);
AmplifyStorageS3 storage = new AmplifyStorageS3();
AmplifyAuthCognito auth = new AmplifyAuthCognito();
AmplifyAPI apiRest = AmplifyAPI();
// Amplify.addPlugin(dataStorePlugin);
Amplify..addPlugins([dataStorePlugin, storage, auth, apiRest]);
Amplify.configure(amplifyconfig);
print('Amplify configured');
}
// read all posts from databases
Future readPost() async {
try {
isLoading(true);
var result = await Amplify.DataStore.query(Posty.classType);
print('finish loading request');
result = result.sublist(1);
posts.assignAll(result);
// print(the value of posts is $posts');
} finally {
isLoading(false);
}
}
#override
void onClose() {
// called just before the Controller is deleted from memory
super.onClose();
}
}
And in the UI part:
class TabBody extends StatelessWidget {
TabBody({Key? key}) : super(key: key);
final ReadPostController _controller = Get.put(ReadPostController());
#override
Widget build(BuildContext context) {
_controller.readPost();//if commented out, _controller.post is empty
return Container(
child: Obx(
() => Text('showing:${_controller.posts[1].title}'),
));
}
}
In my understanding, the readPost() function should be called when the ReadPost_controller is initiallized. And the UI will update when the posts = <Posty>[].obs changes. Guys, what am I doing wrong here?
First, when you are calling readPost on onInit you are not awaiting. So change it to:
onInit() async{
...
await readPost();
...
}
Secondly, posts is a RxList so you need to use the assignAll method to update it.
Therefore, in your readPost method, instead of posts.value = reault you need to use posts.assignAll(result)
Calling from the UI works because readPost every time the build method is called by the Flutter framework and actually the UI shows the data from every previous call.
I think try with GetBuilder instead of Obx.
GetBuilder<ReadPostController>(
builder: (value) => Text('showing:${value.posts[1].title}'),
)
and also use update(). in readPost() method.

Riverpod provider is always null

I am using riverpod for my state manegement in my flutter app.
Riverpod offers a feature for combined providers, but my dependent provider does not update and always returns null.
By clicking one of the pins (secrets) on the map, my "selectedSecretProvider" is updated (default is null). This should trigger the initialization of my audio player. And by clicking play, the sound of the current _selectedSecret should play. So my "selectedTrackProvder" is dependent on my "selectedSecretProvider":
final selectedTrackProvider = StateNotifierProvider<SelectedTrack, Track>((ref) {
Secret? selectedSecret = ref.watch(selectedSecretProvider);
return SelectedTrack(selectedSecret);
});
Here is my selectedTrack class:
class SelectedTrack extends StateNotifier<Track> {
SelectedTrack(this.selectedSecret) : super(Track.initial());
Secret? selectedSecret;
#override
void dispose() {
...
}
void initAudioPlayer() {
...
}
Future<int> play() async {
print(selectedSecret);
return ...
}
}
So why does it always print null, when clicking play?
(Btw. my bottom_panel_sheet is showing the correct data and also consumes the "selectedSecretProvider".)
I wouldn't say the way you're creating your StateNotifierProvider is wrong, but I think the following is a better approach that should solve your problem.
final selectedTrackProvider = StateNotifierProvider<SelectedTrack, Track>((ref) {
return SelectedTrack(ref);
});
class SelectedTrack extends StateNotifier<Track> {
SelectedTrack(this.ref) : super(Track.initial());
final ProviderReference ref;
Future<int> play() async {
final selectedSecret = ref.read(selectedSecretProvider);
print(selectedSecret);
return ...
}
}
This way you don't create a new StateNotifier every time the selectedSecretProvider updates, instead opting to read the current value of the selectedSecretProvider when attempting to call play.

How to clear data from Streams of blocpattern?

My this function isn't clearing the cart.
clearCart(){
_listController.close();
}
Am I supposed to call some other property or implement some other approach in clear cart function?
Here is my CartListBloc code:
class CartListBloc extends BlocBase {
CartListBloc();
var _listController = BehaviorSubject<List<FoodItem>>.seeded([]);
//provider class
CartProvider provider = CartProvider();
//output
Stream<List<FoodItem>> get listStream => _listController.stream;
//input
Sink<List<FoodItem>> get listSink => _listController.sink;
addToList(FoodItem foodItem) {
listSink.add(provider.addToList(foodItem));
}
removeFromList(FoodItem foodItem) {
listSink.add(provider.removeFromList(foodItem));
}
clearCart(){
// What should I put here to clear the bloc of Streams from cart
}
//dispose will be called automatically by closing its streams
#override
void dispose() {
_listController.close();
super.dispose();
}
}
The Stream class has a drain method, which removes all data from a Stream. However, you seem to be trying to clear a BehaviorSubject so you can't use drain (It doesn't actually clear the subject). Instead, you should probably simply add an empty List or null (in which case you need to deal with this null in your UI) to _listController, which will give you a new event with no items.
Edit:
Example:
_listController.add([]); //Now your listeners will receive
//new event with empty list of items