Flutter android_alarm_manager oneShot is not working after close app - android

I want to execute some task (e.g. fetch data from server) in background even app is closed in Flutter App.
So How can I achive this?
Its' better If anyone provide example for that.
I am trying using android_alarm_manager but facing below issue:
I have cloned ahttps://github.com/jsoref/flutter-plugins/tree/master/packages/android_alarm_manager/example
Modified code as below:
void printPeriodic() => printMessage("Periodic!");
void printonDelayed() async {
int i = 0;
while(i < 50) {
printMessage("printonDelayed:" + i.toString());
await sleep1();
i++;
}
Future<String> sleep1() {
return new Future.delayed(const Duration(seconds: 1), () => "1");
}
await AndroidAlarmManager.oneShot(
const Duration(seconds: 1), oneShotID, printOneShot, exact: true);
await AndroidAlarmManager.oneShot(
const Duration(seconds: 1), 2, printonDelayed, wakeup: true, exact: true);
Periodic! is printing even if I close app.
printonDelayed is not printing if I close app
Android Version: 8.1.0

Related

Flutter UI is still freezing when using isolate

I am developing an application in Flutter where I need to implement an image selection function like in instagram.
But there is an issue, my app UI is freezing when trying to get and compress files from user phone gallery.
This is my first experience with flutter isolates, but as far as i know it should work without freezes.
Here is an image for a better understanding of what i want to do.
This is a function that calls getFiles function in isolation.
Here i get paths of user phone gallery files and pass them to another function in order to compress and get files for rendering.
Future fetchImages({ bool fetchMore = false, bool force = false }) async {
if (!fetchMore) {
setState(() => fetched = false);
}
if (force) {
assetsCount = await assetPathEntity!.assetCountAsync;
page = 0;
files.clear();
}
if (assetsCount == 0 || page >= (assetsCount / pageSize)) {
return setState(() => fetched = true);
}
final assetEntities = await assetPathEntity!.getAssetListPaged(page: page++, size: pageSize);
lastCompletedIndex = files.length;
final receivePort = ReceivePort();
final completer = Completer();
getFiles(receivePort.sendPort, assetEntities);
try {
receivePort.listen((filesPaths) {
for (final filePath in filesPaths) {
files.add({
"path": filePath,
"compressedFile": null,
});
}
if (scaledFile == null && files.isNotEmpty) {
scaledFile = File(files.first["path"]);
}
compressAlbumImages();
completer.complete();
setState(() => fetched = true);
}).onError((_) {
compressAlbumImages();
completer.complete();
setState(() => fetched = true);
});
await Future.wait([completer.future]);
} catch (_) { }
finally {
receivePort.close();
}
}
This is getFiles function that runs in isolation
void getFiles(SendPort sendPort, List<AssetEntity> assetEntities) async {
final List<String> filesPaths = [];
for (final assetEntity in assetEntities) {
try {
final file = await assetEntity.file;
if (file != null) {
filesPaths.add(file.path);
}
} catch (_) { }
}
sendPort.send(filesPaths);
}
This is a function that calls compressImages function and adds any value to refresh the list of images
Here i pass the paths and get compressed files for rendering.
void compressAlbumImages() async {
final receivePort = ReceivePort();
final completer = Completer();
compressImages(receivePort.sendPort, files, lastCompletedIndex);
try {
receivePort.listen((compressedFilesWithPath) {
files = compressedFilesWithPath;
fileStreamCt.sink.add(1);
completer.complete();
}).onError((_) {
completer.complete();
});
await Future.wait([completer.future]);
} catch (_) {}
finally {
receivePort.close();
}
return;
}
This is an image compression function that runs in isolation
void compressImages(SendPort sendPort, List<Map<String, dynamic>> files, int startFromIndex) async {
final List<String> filesToBeRemoved = [];
for (int idx = startFromIndex; idx < files.length; idx++) {
final file = files[idx];
try {
final compressedFile = await FlutterNativeImage.compressImage(
file["path"],
quality: 20,
percentage: 20,
targetHeight: 300,
targetWidth: 300,
);
file["compressedFile"] = compressedFile;
} catch (_) {
filesToBeRemoved.add(file["path"]);
}
}
if (filesToBeRemoved.isEmpty) {
return sendPort.send(files);
}
final compressedFilesWithPaths = files
.whereNot((element) => filesToBeRemoved.contains(element["path"]))
.toList();
sendPort.send(compressedFilesWithPaths);
}
And finally i render compressed images
return StreamBuilder(
stream: fileStreamCt.stream,
builder: (ctx, AsyncSnapshot<int> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("LOADING...");
}
if (snapshot.hasError) {
return Text("AN ERROR OCCURRED");
}
return buildAlbumImages();
}
);
Render like this
Image.file(compressedImage);
If in short - When i'm getting user phone gallery files && compress them, my app UI starts freezing.
I don't know why it's freezing.
I tried the same process but with file.readAsBytes() and to render like Image.memory(compressedFileBytes), but it was useless.
I would be very grateful for any help.
Thanks in advance.
In your code you never actually create an isolate. The 'isolate code' in getFile, compressImages etc simply runs on the main isolate and indeed will block the UI.
Per documentation, you create an isolate with Isolate.spawn and pass only the sendPort. The isolate then must send back a receivePort, and the main thread uses that port to send the data you want to isolate to process (like assetEntities), processes it and sends the results back to the main thread. It's a bit complicated, and requires different function signatures than you have here.
Fortunately, a much easier way to accomplish what you want (still using Isolates that won't block the UI) is to use the compute function from the dart:async package:
Change the signature of your getFiles function to Future<List<String>> getFiles(List<AssetEntity> assetEntities) async and do in it what you need to do, returning the list of filesPaths as you do now. Importantly, getFiles must be a top level or a static function, it cannot be a regular class method. Then, where you need the calculation done you use something like var filesPaths = await compute(getFiles, assetEntities). Now, the getFiles function is called in an isolate, and the return value is given back to you on the main isolate. The nice thing is that now this looks a lot like regular await call, no need for sendPorts etc. You can do the same thing for your other heavy calculation methods.
One (big) constraint with isolates is the type of argument you can pass to and from an isolate, see here. Those same constraints apply here, because under the hood the compute function also uses sendPorts etc.

Firebase cloud messaging open rate shows 0 for iOS all the time

UPDATE: It turns out none of the events staring with "notification_" is working for iOS. It is not "notification_open" only issue.
The application I'm working on is based on flutter and published for both Android and iOS.
We have been sending push notifications from firebase panel for the last 6 months but we have never seen open rates for the iOS devices.
Here is an example campaign with 1510 successful messages;
From the same campaign, Android has 260 sends and 31 opens:
But, when we check iOS, we have 1250 sends and 0 opens:
It is like this for all the campaigns we sent. No data for iOS. And, in analytics, iOS has the most user interactions already.
I'm handling the messages like below for both of the OSes:
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await FirebaseMessaging.instance.requestPermission(
provisional: true,
);
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
FirebaseMessaging.instance
.getInitialMessage()
.then((RemoteMessage message) async{
if (message != null) {
final prefs = await SharedPreferences.getInstance();
for (var dataObject in message.data.entries) {
if(dataObject.key == "customURL"){
await prefs.setString('customURL', dataObject.value);
}else{
await prefs.setString('customURL', "");
}
}
}
});
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((_) {
runApp(MyApp());
});
}
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
for (var dataObject in message.data.entries) {
if(dataObject.key == "customURL"){
final prefs = await SharedPreferences.getInstance();
await prefs.setString('customURL', dataObject.value);
}
}
print("_receiveNotification onBackgroundMessage" + message.notification.title);
log("Handling a background message");
}
Do I need to add a custom event handler for iOS? I searched most of the docs but couldn't see any indicator for it.

How do I run code whenever the user opens a particular screen?

I have the code below that I thought would run whenever the given screen is reached, i.e. whenever the user goes to this screen. This screen creates some temporary files for the user. I don't need them after the user leaves the screen, so I wanted to flush them everytime the user reaches this screen. However, the line with await cleanupTempAudioFiles(); doesn't seem to be doing its job.
#override
void initState() {
super.initState();
initialize();
}
void initialize() async {
uid = auth.currentUser;
filesInProgressFileDirString = systemTempDir.path + '/App/AppAudioFiles/FilesInProgress/';
fileInProgressFileDir = await Directory(fileInProgressFileDirString).create(recursive: true);
myRecorder = await FlutterSoundRecorder().openRecorder();
myPlayer = await FlutterSoundPlayer().openPlayer();
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 10),
)..addListener(() {
setState(() {});
});
controller?.reset();
await cleanupTempAudioFiles(); //This code seems to not be running every time the screen is opened
setState(() {
sendableFileExists = 0;
});
}
Future<void> cleanupTempAudioFiles() async {
final dir = Directory(filesInProgressFileDir.path);
final List<FileSystemEntity> files = await dir.list().toList();
files.forEach((file) async {
if (file.path == filesInProgressFileDir.path + currentAppFilename) {
await file.delete();
}
if (file.path == filesInProgressFileDir.path + currentAppFilename + 'High.mp3') {
await file.delete();
}
if (file.path == filesInProgressFileDir.path + currentAppFilename + 'Low.mp3') {
await file.delete();
}
});
}
The initState() function is called when your object is put into the widget tree. This is not the same as every time it is displayed. Navigator.pop() for example will show the screen without re-inserting the widget into the tree. see https://api.flutter.dev/flutter/widgets/State/initState.html
To run every time the user sees a widget you should put the code into the build function or consider #overriding dispose(), didChangeDependencies() or
didUpdateWidget() instead to get the right part of the widget lifecycle.

React Native Track Player - Seek to precise position doesn't work

I'm developing a music streaming app that uses React Native Track Player to handle music in app and in the background.
I'm fetching music from a server through Socket.IO and I get all the song as expected.
In this application, the admin can choose to start the streaming and then all the users connected to the app will receive the song and will start on their device. If a user connect when a song is already started, the user will receive the song and the current position. So, if a song is already started, will seek to the current position.
The problem is that not always the seek works. Sometimes it starts from 0, other times in an apparently random position. Only a few times the position seek well.
I thought that the song wasn't buffered completely, but if I try to output the current position and buffered position before and after the seek, it is right.
How could I resolve this? I should wait that all the song is buffered, and then seek?
If this module can't do this, are there other valid modules?
I tested this only on Android for now, idk if on iOS works.
Here the code:
This is the streaming function, listen from the server for songs...
socketStreaming.on("firstSong", async (song) => {
await TrackPlayer.reset();
await TrackPlayer.add({
id: song.id,
url: song.url,
title: song.title,
artist: song.artist,
artwork: song.artwork,
});
console.log(
`${await TrackPlayer.getBufferedPosition()} - ${await TrackPlayer.getPosition()}`
);
await TrackPlayer.getBufferedPosition().then(async () => {
if (song.seekTo !== undefined) await TrackPlayer.seekTo(~~song.seekTo);
console.log(
`${await TrackPlayer.getBufferedPosition()} - ${await TrackPlayer.getPosition()}`
);
});
await TrackPlayer.play();
console.log(
`${await TrackPlayer.getBufferedPosition()} - ${await TrackPlayer.getPosition()}`
);
setSrcButton(pauseImage);
});
socketStreaming.on("nextSong", async (song) => {
await TrackPlayer.add({
id: song.id,
url: song.url,
title: song.title,
artist: song.artist,
artwork: song.artwork,
});
});
socketStreaming.on("seek", async (second) => {
await TrackPlayer.seekTo(second);
await TrackPlayer.play();
setSrcButton(pauseImage);
});
This is the setup function, that setup the player...
await TrackPlayer.setupPlayer({
waitForBuffer: true,
});
await TrackPlayer.updateOptions({
capabilities: [
TrackPlayer.CAPABILITY_PLAY,
TrackPlayer.CAPABILITY_PAUSE,
TrackPlayer.CAPABILITY_SEEK_TO,
],
compactCapabilities: [
TrackPlayer.CAPABILITY_PLAY,
TrackPlayer.CAPABILITY_PAUSE,
],
notificationCapabilities: [
TrackPlayer.CAPABILITY_PLAY,
TrackPlayer.CAPABILITY_PAUSE,
],
});
Both functions are inside a useEffect(callback, []), so will be called only on startup
Thanks for the help :)
I was stuck with the same problem.
Resolved it by playing the track first and then seek to the required position.
Something like this,
await TrackPlayer.play();
await TrackPlayer.seekTo(second);

React Native Background Download

I am about to build an app which locally stores multiple MySqlite *.db files. These are used to populate the app with data.
The app should regularly check if there is a newer version for one of these files and, if so, download it and replace the old version of the file.
This update process should happen in the background, while the app is inactive or even closed.
I have seen several plugins, which can be used to execute tasks in the Background (like https://www.npmjs.com/package/react-native-background-task). This could be used to check for updates regularly, but the time limit of 30s on iOS will probably not be enough to download the *.db file. Also, the plugin forces a minimum Android API Version of 21.
My question is: Is it possible to poll for updates in the background AND download them, replacing old files?
I found some useful plugins.
1. react-native-fetch-blob
https://github.com/wkh237/react-native-fetch-blob/wiki/Classes#rnfetchblobconfig
It has IOSBackgroundTask option.
RNFetchBlob
.config({
path : dest_file_path,
IOSBackgroundTask: true,
overwrite: true,
indicator: true,
})
.fetch('GET', download_url, {
//some headers ..
})
.progress( (received, total) => {
console.log('progress : '+ received + ' / ' + total);
})
.then((res) => {
console.log('# The file saved to :', file_path);
})
By the way, it looks doesn't work properly.
Not sure if I miss something...
2. react-native-fs
https://github.com/itinance/react-native-fs#downloadfileoptions-downloadfileoptions--jobid-number-promise-promisedownloadresult-
const ret = RNFS.downloadFile({
fromUrl: download_url,
toFile: dest_file_path,
connectionTimeout: 1000 * 10,
background: true,
discretionary: true,
progressDivider: 1,
resumable: (res) => {
console.log("# resumable :", res);
},
begin: (res) => {
// start event
},
progress: (data) => {
const percentage = ((100 * data.bytesWritten) / data.contentLength) | 0;
console.log("# percentage :", percentage);
},
});
jobId = ret.jobId;
ret.promise.then((res) => {
console.log('Download finished.');
RNFS.completeHandlerIOS(jobId);
jobId = -1;
}).catch(err => {
console.log('error');
jobId = -1;
});
It looks working well.
By the way, when I try download in background via push notification, it doesn't start download unless I open the app.
Anyone can solve this problem?
For download in the background, the best module I have used was react-native-background-downloader, which have pause, resume and the download percentage.
import RNBackgroundDownloader from 'react-native-background-downloader';
let task = RNBackgroundDownloader.download({
id: 'dbfile',
url: 'https://link-to-db/MySqlite.db'
destination: `${RNBackgroundDownloader.directories.documents}/MySqlite.db`
}).begin((expectedBytes) => {
console.log(`Going to download ${expectedBytes} bytes!`);
}).progress((percent) => {
console.log(`Downloaded: ${percent * 100}%`);
}).done(() => {
console.log('Download is done!');
}).error((error) => {
console.log('Download canceled due to error: ', error);
});
// Pause the task
task.pause();
// Resume after pause
task.resume();
// Cancel the task
task.stop();
This module is also suitable for iOS and you can download multiple files in a row in the background.

Categories

Resources