How to store data after 24 hours in flutter? / How to update UI after some time when app is closed/killed in flutter? - flutter

I am making an app with flutter. I want to store data after 24 hours and update UI in app.
I try with Timer.periodic() but it does not count the time when app is close. It only works when the application is open.
Is it possible to execute a function after a specific time even if the app is closed?
Here is my current code:
void callbackDispatcher() async{
Workmanager().executeTask((task, inputData) {
switch(sdDaily){
case 'StoreDataDaily':
storeData.storeDailyData();
break;
default:
}
return Future.value(true);
});
}
void main() async{
WidgetsFlutterBinding.ensureInitialized();
Directory directory = await path_provider.getApplicationDocumentsDirectory();
print(directory.path);
Hive.init(directory.path);
await Hive.initFlutter(directory.path);
Hive.registerAdapter(UserAdapter());
Hive.registerAdapter(WaterAdapter());
Hive.registerAdapter(WeekAdapter());
Get.put(UserController());
Get.put(WaterController());
await Hive.openBox<User>('data');
await Hive.openBox<Water>('water_data');
await Hive.openBox<Week>('week_data');
await notificationPlugin.showNotification();
await Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
var uniqueId = DateTime.now().second.toString();
var userBox = Hive.box<User>('data');
if(userBox.get(0)?.status == 1){
await Workmanager().registerOneOffTask(uniqueId, sdDaily,);
}
runApp(const MyApp());
}

You can use : flutter_background_service. to execute background services and it'll also help you sending a custom notification when you are actually going to store that data later.

You can use firebase cloud funcitons to do schedule tasks or whatever you want to do even if app is closed or killed.

Related

Flutter how to save data locally while phone screen is off

I wanna save data locally while app running in background and phone screen is off , how can u do that using hive DB.
I just try to save data locally while app is running everything is fine now I want to do the same thing while screen phone is off
I don't think Hive supports opening boxes in multiple isolates, so you will have to close the box in the main isolate, update it in your background isolate and reopen it in the main isolate.
This might help
Here is an example code of communication between two isolates:
import 'dart:io';
import 'dart:async';
import 'dart:isolate';
Future<SendPort> initIsolate() async {
Completer completer = new Completer<SendPort>();
ReceivePort isolateToMainStream = ReceivePort();
isolateToMainStream.listen((data) {
if (data is SendPort) {
SendPort mainToIsolateStream = data;
completer.complete(mainToIsolateStream);
} else {
print('[isolateToMainStream] $data');
}
});
Isolate myIsolateInstance = await Isolate.spawn(myIsolate,isolateToMainStream.sendPort);
return completer.future;
}
void myIsolate(SendPort isolateToMainStream) {
ReceivePort mainToIsolateStream = ReceivePort();
isolateToMainStream.send(mainToIsolateStream.sendPort);
mainToIsolateStream.listen((data) {
print('[mainToIsolateStream] $data');
exit(0);
});
isolateToMainStream.send('This is from myIsolate()');
}
void main() async {
SendPort mainToIsolateStream = await initIsolate();
mainToIsolateStream.send('This is from main()');
}

Deep linking is not working when app is not in background (app closed/killed)

I have implemented this method so that when a user clicks the dynamic link it will be redirected to a specific page. Everything works alright while the app is running, but when I kill/close the app and try to do the same thing, it opens the app on the initial screen (Home Page). How can I make it work in this case?
Future<void> initDynamicLinks() async {
FirebaseDynamicLinks.instance.onLink.listen((dynamicLinkData) {
id = dynamicLinkData.link
.toString()
.substring(dynamicLinkData.link.toString().lastIndexOf('/') + 1);
Get.to(
() => Page(
id: id,
),
);
}).onError((error) {
if (kDebugMode) {
print(error.message);
}
});
}
void initState() {
// TODO: implement initState
initDynamicLinks();
super.initState();
}
I think .onLink.listen() function only get hit when app is resumed from background.
If you want your deeplink work when app have a fresh start then just put this code above .onLink.listen() function...
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final PendingDynamicLinkData data = await FirebaseDynamicLinks.instance.getInitialLink();
final Uri deepLink = data?.link;
// Here you should navigate to your desired screen
Hope it helps you

How to get App last Update time in flutter?

I want to get last app updates time in flutter, I tried but I can't get that time.
Hei,
I don't know if there is a package which manages this but I think you can manage it with some combinations. Add shared_preferences and package_info_plus as dependencies into your pubspec.yaml file as usual.
Then in a uppest stateful widget in your widget tree, define a function as below (runApp -> MyApp -> HomePage(stateful) on Homepage for example):
//import on top
import 'package:shared_preferences/shared_preferences.dart';
import 'package:package_info_plus/package_info_plus.dart';
// .........
void checkUpdateTime() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
SharedPreferences prefs = await SharedPreferences.getInstance();
var previousVersion = prefs.getString("version");
var previousTime = prefs.getString("latestTimeUpdated");
String currentVersion = packageInfo.version + "+" + packageInfo.buildNumber;
String now = DateTime.now().toUtc().toString();
// First launch after app downloaded
if(previousVersion == null && previousTime == null){
await prefs.setString("latestTimeUpdated", now);
await prefs.setString("version", currentVersion);
}
// There is previous version instance saved before so check if its the same with the current version
if (previousVersion != null) {
// check saved version and current version is different
if (previousVersion != currentVersion) {
// Update time
await prefs.setString("latestTimeUpdated", now);
await prefs.setString("version", currentVersion);
}
// Do nothing if saved version and current version is the same
}
}
Do not forget to call the function on initState:
#override
void initState() {
checkUpdateTime();
super.initState();
}
Basically;
This will cross-check your app's current version and last saved version. If these are not same, it will update the latestTimeUpdated. Then in your app anywhere you want:
SharedPreferences prefs = await SharedPreferences.getInstance();
String updateTimeUTCString = prefs.getString("latestTimeUpdated");
Format this updateTimeUTCString as you wish and use it.
I hope this becomes useful for you.

Is there a way to skip await if it's too long? (Flutter)

I use async await in main so the user has to wait in the splash screen before entering the app.
void main() async {
await Firebase.initializeApp();
String? x;
await FirebaseDatabase.instance.ref().child("data").once().then((snapshot) {
Map data = snapshot.snapshot.value as Map;
x = jsonEncode(data);
});
return ChangeNotifierProvider<DataModel>.value(
value: DataModel(data: x),
child: MaterialApp()
);
}
If there are users entering the app without an internet connection, they will be stuck on the splash screen forever. If there are also users with slow internet connection, they will be stuck on the splash screen longer.
So no matter what the internet connection problem is, I want to set a maximum of 5 seconds only to be in the await, if it exceeds, skip that part and go straight into the app.
Pass your Firebase api call to a function, make the method return future,
Future<String?> getData() async {
await FirebaseDatabase.instance.ref().child("data").once().then((snapshot) {
Map data = snapshot.snapshot.value as Map;
return jsonEncode(data);
});
}
Then on main method, attach a timeout with that method you created
void main() async {
await Firebase.initializeApp();
String? x = await getData().timeout(Duration(seconds: 5), onTimeout: () {
return null };
return ChangeNotifierProvider<DataModel>.value(
value: DataModel(data: x),
child: MaterialApp()
);
}
So if you api takes more then 5 seconds of time, then it exit and return null
you can User Race condition or Future.timeout
Race condition using future.any
final result = await Future.any([
YourFunction(),
Future.delayed(const Duration(seconds: 3))
]);
in above code one who finish first will give result,
so if your code takes more than 3 sec than other function will return answer

how to save data in Hive database when receiving data in the background?

I have an issue saving data to Hive when receiving Firebase Cloud Messaging (FCM) push notification data when the app is in the background.
I have a static method to set up hive like this
static Future<void> setUpHive() async {
try {
await Hive.initFlutter();
if (!Hive.isBoxOpen("Box Name")) {
await Hive.openBox("Box Name");
}
} catch (error) {
print(error.toString());
}
}
I use that setUpHive static method in main function like this
Future<void> main() async {
await HiveHelper.setUpHive();
runApp(
MyApp(),
);
}
when the app is in the background, and then it receives FCM message, then this code below will be called. after that I try change the data stored in the Hive box
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// when receive FCM message when app is in the background, this block will be executed
// set up the hive first
await HiveHelper.setUpHive();
// then I try to change the data stored in the Hive box
final myBox = Hive.box("BOX NAME");
myBox.put("key", 12345);
}
it seems okay after receiving FCM background data, but when I fully close the app, and the main called again I have error when trying to open the box like this
static Future<void> setUpHive() async {
try {
await Hive.initFlutter();
if (!Hive.isBoxOpen("Box Name")) {
await Hive.openBox("Box Name"); // Error in this line
}
} catch (error) {
print(error.toString());
}
}
the error is:
HiveError: This should not happen. Please open an issue on GitHub.
E/flutter (13142): [ERROR:flutter/lib/ui/ui_dart_state.cc(199)]
Unhandled Exception: HiveError: This should not happen. Please open an
issue on GitHub. E/flutter (13142): #0 BinaryReaderImpl.readFrame
(package:hive/src/binary/binary_reader_impl.dart:250:7)
E/flutter
I try to find the solution, and I find similar issue from here about Using Hive DB in a Background Process and it is said
leisim:
Unfortunately, Hive does not support opening boxes in multiple
isolates. That means you can either close the box in the main isolate,
update it in your background isolate and reopen it in the main isolate
or you pass the data from the background to the main isolate and
perform the update there...
I am new in Flutter, and I don't understand what he said. please help :(
You can try the following code. The basic idea is to send data from background isolate to main isolate.
Future<void> backgroundMessageHandler(RemoteMessage msg){
IsolateNameServer.lookupPortByName('main_port')?.send(msg);
}
#override
void initState(){
super.initState();
ReceivePort receivePort = ReceivePort();
IsolateNameServer.registerPortWithName(receivePort.sendPort,'main_port');
receivePort.listen((message) {
if(message is RemoteMessage){
//TODO: save your data in hive box
}
}
}
You need to close your hive box in the main isolate once app goes into background. When it does, you need to CRUD in the background isolate. If you want to sync data between two isolates (because they don't share the same hive data) then you need a two way communication between isolates.
Here is an example code of communicating between two isolates.
import 'dart:io'; // for exit();
import 'dart:async';
import 'dart:isolate';
Future<SendPort> initIsolate() async {
Completer completer = new Completer<SendPort>();
ReceivePort isolateToMainStream = ReceivePort();
isolateToMainStream.listen((data) {
if (data is SendPort) {
SendPort mainToIsolateStream = data;
completer.complete(mainToIsolateStream);
} else {
print('[isolateToMainStream] $data');
}
});
Isolate myIsolateInstance = await Isolate.spawn(myIsolate, isolateToMainStream.sendPort);
return completer.future;
}
void myIsolate(SendPort isolateToMainStream) {
ReceivePort mainToIsolateStream = ReceivePort();
isolateToMainStream.send(mainToIsolateStream.sendPort);
mainToIsolateStream.listen((data) {
print('[mainToIsolateStream] $data');
exit(0);
});
isolateToMainStream.send('This is from myIsolate()');
}
void main() async {
SendPort mainToIsolateStream = await initIsolate();
mainToIsolateStream.send('This is from main()');
}
for more go to https://medium.com/#lelandzach/dart-isolate-2-way-communication-89e75d973f34