How to make a stopwatch that takes a widget off the air? - flutter

I need to know how to make this stopwatch, which after 48 hours disables a widget, which in my case is a button. Can someone explain to me how to do it? What classes to use?
I tried to use this, but don't works:
var timer = Timer(Duration(seconds: 1), () => print('done'));

it seems to me that you want this button to be disabled after 2 days of the app that was installed, you need persist the date on the device so that after app-restarts the date will be itself, you need to use a package the persists the data on the device. i recommend shared_preference which is easy to use.
for your case, in the screen where you use the button you need to do this
import 'package:shared_preferences/shared_preferences.dart';
class MyFirstStatefullScreen extends StatefullWidget {
MyFirstStatefullScreenState createState() => MyFirstStatefullScreenState();
}
class MyFirstStatefullScreenState extends State<MyFirstStatefullScreen>{
// some other code that u wrote
bool shouldButtonBeActive = true;
#override
void initState() {
Future.delayed(Duration(0))
.then((_) {
SharedPreferences sharedPrefs= await SharedPreferences.getInstance();
final deadLine = sharedPrefs.getString('deadLine');
if(deadLine == null) {
// this is the first time the app has been installed on the device
// so we need to set the deadLine to N number of days after the installation
final deadLineDate = DateTime.now().add(Duration(days: 2)); // 2 days from the date that the app was installed;
sharedPrefs.setString(deadLineDate.toIso8601String()); // set it so we can check on the successfull state
return;
}
final deadLineDate = DateTime.parse(deadLine); // since we stored it as a string;
/// the deadline is set and is not null
if(DateTime.now().compareTo(deadLineDate) == 0) {
we are successfull, N hours have passed since the intial instalation, now we can disable the button
shouldButtonBeActive = false; // the button should be disabled
}
})
}
Widget build(context) {
// in your UI where you use the button
MaterialButton(
child: Text("Button"),
onPressed: shouldButtonBeActive ? func() : null
)
}
}
PS: we are using the Future inside the initState, because initState dose not allow async ( api calls, storage access) in it.

Related

How can I make a Flutter application that is connected to Google Sheets that updates every time a cell is edited?

I am currently making a monitoring application that integrates Arduino to an application, and I am using Google Sheets as the data transfer medium. Arduino > GSheets > App. Say I want to be able to update the displayed temperature on the app based on the latest logged temperature on the GSheet. Can this process be done without needing to restart the app? My current code needs a restart to see cell updates in the app. I am using Flutter.
Based on your title:
When the app is initially loaded, you need to store the initial value of Gsheet cell in shared preferences.
Then create a periodic function that (Timer class of async library) makes a request to the Gsheets. The result of the request should have the last cell edited.
Access the cell you stored previously in shared preferences and compare it with the result from the Gsheets periodic function. If there is a difference, update the UI & update shared preferences.
Periodic function sample:
// Using provider
class Subscriptionsprovider with ChangeNotifier {
var _newValue;
dynamic get newValue => _newValue;
Timer? _timer;
subscriptionsUpdate() {
const sec = Duration(seconds: 1); // manage the timing properly
_timer = Timer.periodic(sec, (Timer t) async {
var gValue = await _checkGsheets();
var sValue = await _checkSharedPrefs();
if(gValue != sValue){
// for the UI
_newValue = gValue;
notifyListeners();
await_updateSharedPrefernces(_newValue);
}
}
#override
void dispose() {
super.dispose();
_timer!.cancel();
}
}
To bring to the UI:
#override
void initState() {
super.initState();
context.read<Subscriptionsprovider>().subscriptionsUpdate();
}
....
#override
Widget build(BuildContext context) {
final subscriptionProvider = context.watch<Subscriptionsprovider>();
return Container(child:Text(subscriptionProvider.newValue));
....
Important: Remember to setup your provider properly. If you don't understand how providers work, you need to do more research.

How can I get a value from an if statement and work out the difference

I have a function that checks if a user has the app open or closed. If opened take the current time and store it in a variable, if the app is closed, do the same. then it should work out the difference and print it back.
I'm trying to work out how long the user has spent on the app.
I just not sure how to get it to work, I've tried to assign it to a global and use it from there but that did not work, I've tried sending the values to another function but I keep getting null returned.
What can I do to fix this?
*.dart
activeTimer(value) {
print(value);
if (value == true) {
startTimer();
print("TRUE 1");
DateTime dateTimeStart = DateTime.now();
} else {
print("FALSE 1");
stopTimer();
DateTime dateTimeEnd = DateTime.now();
};
final differenceInDays = dateTimeEnd.difference(dateTimeStart).inMinutes;
print(differenceInDays);
}
You can check when your app is being closed (or placed in background), so just initialize a variable when the app starts and calculate the time when the app closes:
in your main.dart file
late DateTime app_start;
#override
initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
app_start = DateTime.now();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
// check if app is closed -> substract current time from app_start
// save to local storage or send to cloud or smth
}
I highly recommend you to use a Stream for this purpose.
For example:
Stream<int> tick({required int ticks}) {
return Stream.periodic(Duration(seconds: 1), (x) => ticks + x + 1)
.take(ticks);
}
That's very similar to what you can find in this Bloc Tutorial - link

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.

Storing data in class level global variables

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

Flutter provider in initState

I'm currently trying Provider as a state management solution, and I understand that it can't be used inside the initState function.
All examples that I've seen call a method inside a derived ChangeNotifier class upon user action (user clicks a button, for example), but what if I need to call a method when initialising my state?
Motivation:
Creating a screen which loads assets (async) and shows progress
An example for the ChangeNotifier class (can't call add from initState):
import 'package:flutter/foundation.dart';
class ProgressData extends ChangeNotifier {
double _progress = 0;
double get progress => _progress;
void add(double dProgress) {
_progress += dProgress;
notifyListeners();
}
}
You can call such methods from the constructor of your ChangeNotifier:
class MyNotifier with ChangeNotifier {
MyNotifier() {
someMethod();
}
void someMethod() {
// TODO: do something
}
}
Change your code to this
class ProgressData extends ChangeNotifier {
double _progress = 0;
double get progress => _progress;
void add(double dProgress) async {
// Loading Assets maybe async process with its network call, etc.
_progress += dProgress;
notifyListeners();
}
ProgressData() {
add();
}
}
In initState all the of(context) things don't work correctly, because the widget is not fully wired up with every thing in initState.
You can use this code:
Provider.of<ProgressData>(context, listen: false).add(progress)
Or this code:
Future.delayed(Duration.zero).then(_){
Provider.of<ProgressData>(context).add(progress)
}):
So an AssetLoader class which reports on its progress will look something like this, I guess:
import 'package:flutter/foundation.dart';
class ProgressData extends ChangeNotifier {
double _progress = 0;
ProgressData() {
_loadFake();
}
Future<void> _loadFake() async {
await _delayed(true, Duration(seconds: 1));
_add(1.0);
await _delayed(true, Duration(seconds: 2));
_add(2.0);
await _delayed(true, Duration(seconds: 3));
_add(3.0);
}
// progress
double get progress => _progress;
// add
void _add(double dProgress) {
_progress += dProgress;
notifyListeners();
}
// _delayed
Future<dynamic> _delayed(dynamic returnVal, Duration duration) {
return Future.delayed(duration, () => returnVal);
}
}
As Fateme said:
the widget is not fully wired up with everything in initState
Also, you can use something like this in your initState
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
Provider.of<ProgressData>(context, listen: false).add(5);
});
I think it's more standard!
Be aware that you should use the correct context! I mean the context of the Builder!
The problem here lies with the fact that context does not exist yet in initState as extensively explained by the other answers. It doesn't exist because it hasn't yet been made a part of the widget tree.
Calling a method
If you're not assigning any state and only calling a method then initState would be the best place to get this done.
// The key here is the listen: false
Provider.of<MyProvider>(context, listen: false).mymethod();
The code above is allowed by Flutter because it doesn't have to listen for anything. In short, it's a one off. Use it where you only want to do something instead of read/listen to something.
Listening to changes
Alternatively, if you need to listen to changes from Provider then the use of didChangeDependencies would be the best place to do so as context would exist here as in the docs.
This method is also called immediately after initState.
int? myState;
#override
void didChangeDependencies() {
// No listen: false
myState = Provider.of<MyProvider>(context).data;
super.didChangeDependencies();
}
If you've never used didChangeDependencies before, what it does is get called whenever updateShouldNotify() returns true. This in turn lets any widgets that requested an inherited widget in build() respond as needed.
I'd usually use this method in a FutureBuilder to prevent reloading data when data already exists in Provider after switching screens. This way I can just check Provider for myState and skip the preloader (if any) entirely.
Hope this helps.