I'm trying to use Flutter_downloader to make my app able to download some attachment file and it work just fine. So I make the Isolate into class to tidy things up and tried to use the progress so I can show the download progress in my UI but the progress stays at 0.
How to fix this ? The download work just fine.
Flutter Downloader Isolate Code:
class DownloaderIsolate {
//Callback for the Flutter_Downloader
static downloadCallback(String id, DownloadTaskStatus status, int progress) {
final SendPort send =
IsolateNameServer.lookupPortByName('downloader_send_port')!;
send.send([id, status, progress]);
}
static setupDownloaderPort({int? progress}) {
ReceivePort _port = ReceivePort();
IsolateNameServer.registerPortWithName(
_port.sendPort, 'downloader_send_port');
FlutterDownloader.registerCallback(DownloaderIsolate.downloadCallback);
_port.listen((dynamic data) {
String id = data[0];
DownloadTaskStatus status = data[1];
progress = data[2];
});
}
}
My UI Page Code:
int? progress = 0;
void initState(){
DownloaderIsolate.setupDownloaderPort(progress: progress);
}
Widget build(BuildContext context) {
//Some Parents Code
//Trying to show the progress in String
Text(progress.toString()),
//Also Trying to show the progress
Text('$progress'),
}
The problem might be that you need to "setState" in order for your UI to update, but you can't setState in your isolate class. You can try the following:
Flutter Downloader Isolate Code:
static setupDownloaderPort({required Function(int) updateProgress}) {
ReceivePort _port = ReceivePort();
IsolateNameServer.registerPortWithName(
_port.sendPort, 'downloader_send_port');
FlutterDownloader.registerCallback(DownloaderIsolate.downloadCallback);
_port.listen((dynamic data) {
String id = data[0];
DownloadTaskStatus status = data[1];
updateProgress(data[2]);
});
}
UI Page:
int? progress = 0;
void initState(){
DownloaderIsolate.setupDownloaderPort(updateProgress: (_p) {
progress = _p;
});
super.initState();
}
Widget build(BuildContext context) {
//Some Parents Code
//Trying to show the progress in String
Text(progress.toString()),
//Also Trying to show the progress
Text('$progress'),
}
Related
I'm using the flutter_downloader package to download files with my app.
The progress notification is working nicely. but my ReceivePort is not listening to the progress.
final ReceivePort port = ReceivePort();
#override
void initState() {
super.initState();
IsolateNameServer.registerPortWithName(
port.sendPort, 'downloader_sendport');
port.listen((dynamic data) async {
log('data: $data'); // don't work
});
FlutterDownloader.registerCallback(downloadCallback);
}
#pragma('vm:entry-point')
static void downloadCallback(
String id, DownloadTaskStatus status, int progress) {
log("downloadCallback => $id, $status, $progress"); // works
final SendPort? send =
IsolateNameServer.lookupPortByName('downloader_sendport');
send?.send([id, status, progress]);
}
change status to primitive value in send method
The problem was with sending DownloadTaskStatus:
solution:
send?.send([id, status.value, progress]);
On Flutter 3.7 platform channels can run on any isolate. So I tried this sample,
import ‘package:flutter/services.dart’;
import ‘package:shared_preferences/shared_preferences.dart’;
void main() {
// Identify the root isolate to pass to the background isolate.
// (API introduced in Flutter 3.7)
RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
Isolate.spawn(_isolateMain, rootIsolateToken);
}
void _isolateMain(RootIsolateToken rootIsolateToken) async {
// Register the background isolate with the root isolate.
BackgroundIsolateBinaryMessenger
.ensureInitialized(rootIsolateToken);
// You can now use the shared_preferences plugin.
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
print(sharedPreferences.getBool(‘isDebug’));
}
I can read from data on shared_preferences in this sample okey. But how can I use this feature anywhere in my app? How can I set or read data using this isolate on initState for example?
Basically you need to implement communication between isolates. You can read more about it here
Here is an example, you can change flutter_secure_storage that i used with shared_preferences package
import 'dart:async';
import 'dart:isolate';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class CreationEvent {
final RootIsolateToken isolateToken;
final SendPort sendPort;
CreationEvent(this.isolateToken, this.sendPort);
}
class DeletetionEvent {}
class ReadEvent {
final String key;
const ReadEvent(this.key);
}
class ReadResult {
final String key;
final String? content;
const ReadResult(this.key, this.content);
}
class IsolateIO {
IsolateIO._();
final _toBgPort = Completer();
final Map<Object, Completer> _completerMap = {};
Isolate? _isolate;
StreamSubscription? _fromBgListener;
void start() async {
RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
ReceivePort fromBG = ReceivePort();
_fromBgListener = fromBG.listen((message) {
// setup process
if (message is SendPort) {
_toBgPort.complete(message);
return;
}
if (message is ReadResult) {
_completerMap['read:${message.key}']?.complete(message.content);
_completerMap.remove('read:${message.key}');
}
});
_isolate = await Isolate.spawn(
(CreationEvent data) {
final worker = IsolateWorker(data.isolateToken, data.sendPort);
worker.listen();
},
CreationEvent(rootIsolateToken, fromBG.sendPort),
);
}
Future<String?> readFromStorage(String key) async {
// make sure isolate created with ports
final port = await _toBgPort.future;
// store completer
final completer = Completer<String?>();
_completerMap['read:$key'] = completer;
// send key to be read
port.send(ReadEvent(key));
// return result
return completer.future;
}
void stop() async {
if (_toBgPort.isCompleted) {
final port = await _toBgPort.future;
port.send(DeletetionEvent());
}
_fromBgListener?.cancel();
_isolate?.kill(priority: Isolate.immediate);
}
static final i = IsolateIO._();
}
class IsolateWorker {
final RootIsolateToken rootIsolateToken;
final SendPort toMain;
final FlutterSecureStorage storage;
StreamSubscription? subs;
IsolateWorker(
this.rootIsolateToken,
this.toMain, {
this.storage = const FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
),
}) {
// Register the background isolate with the root isolate.
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
}
void listen() {
ReceivePort fromMain = ReceivePort();
toMain.send(fromMain.sendPort);
subs = fromMain.listen((message) => onMessage(message));
}
void onMessage(dynamic message) async {
if (message is DeletetionEvent) {
subs?.cancel();
return;
}
if (message is ReadEvent) {
final rawJson = await storage.read(key: message.key);
toMain.send(ReadResult(message.key, rawJson));
}
}
}
class View extends StatefulWidget {
const View({super.key});
#override
State<View> createState() => _ViewState();
}
class _ViewState extends State<View> {
String username = '';
#override
void initState() {
super.initState();
IsolateIO.i.start();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final name = await IsolateIO.i.readFromStorage('username');
setState(() {
username = name ?? '';
});
});
}
#override
void dispose() {
IsolateIO.i.stop();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
child: Text(username),
);
}
}
I have a getx controller and a method to update data in the database, I just wonder that how can I refresh of update the UI constantly after fetching the API? Here is my controller
class AdditionalContactController extends GetxController {
var additionalContactList = <AdditionalContact>[].obs;
var isLoading = true.obs;
UserController _userController = Get.find();
#override
void onInit() {
super.onInit();
_userController.getMail().then((value) async {
await _userController.getUser(value);
getAdditionalContacts(_userController.user.value.id);
});
}
//Update contact
Future<bool> updateContact({...}) async {
var response = await http.post(
Uri.parse(
"https://..."),
body: {
...
});
var jsonData = jsonDecode(response.body);
if (jsonData == "failed") {
return false;
}
return true;
}
}
you can use the ever worker to call a method that executes every time an Rx have assigned with a new value:
class AdditionalContactController extends GetxController {
var additionalContactList = <AdditionalContact>[].obs;
#override
void onInit() {
super.onInit();
ever(additionalContactList, methodToExecute)
});
}
methodToExecute(list) {
/* Code that will execute every time the additionalContactList changes */
}
now everytime additionalContactList is changed like as example if we assign a new List to it:
additionalContactList.value = [];
Then the methodToExecute() will be executed automatically, and will do every single time.
I develop a web app in Flutter and I want to load a file from file system. In order to do that I use the following code:
static Future<Uint8List> chooseImage(dynamic parent, dynamic provider) async {
Uint8List uploadedImage;
final completer = Completer<List<String>>();
InputElement uploadInput = FileUploadInputElement();
uploadInput.accept = 'image/*';
uploadInput.click();
uploadInput.addEventListener('change', (e) async {
final files = uploadInput.files;
Iterable<Future<String>> resultsFutures = files.map((file) {
final reader = FileReader();
reader.readAsDataUrl(file);
reader.onError.listen((error) => completer.completeError(error));
return reader.onLoad.first.then((_) async {
String result = reader.result as String;
uploadedImage = base64Decode(result.substring(22, result.length));
return reader.result as String;
});
});
final results = await Future.wait(resultsFutures);
completer.complete(results);
});
document.body.append(uploadInput);
final List<String> images = await completer.future;
parent.setState(() {
parent.pickedImage = uploadedImage;
});
uploadInput.remove();
return uploadedImage;
}
In my app I need to handle the case when the user press the Cancel button in this pop-up:
I have added listener for: onFocus, onSuspen, onSubmit, onEnded, onAbort but none of these events are triggered when that cancel button is pressed.
How can I handle the pop-up cancelation?
I've already answered to a similar question here
You can already find a way to manage this kind of event in the web implementation of the package file_picker.
Because depending of the browser you are using the cancel event might be managed differently the most generic solution would be to rely on a listener on the focus event from the window so when you will lose the focus on your file picker window without any data loaded, it will complete your future with a null value.
Code Sample
import 'dart:html' as html;
import 'dart:async';
Future<html.File?> pickFile(String type) async {
final completer = Completer<List<html.File>?>();
final input = html.FileUploadInputElement() as html.InputElement;
input.accept = '$type/*';
var changeEventTriggered = false;
void changeEventListener(html.Event e) {
if (changeEventTriggered) return;
changeEventTriggered = true;
final files = input.files!;
final resultFuture = files.map<Future<html.File>>((file) async {
final reader = html.FileReader();
reader.readAsDataUrl(file);
reader.onError.listen(completer.completeError);
return file;
});
Future.wait(resultFuture).then((results) => completer.complete(results));
}
void cancelledEventListener(html.Event e) {
html.window.removeEventListener('focus', cancelledEventListener);
// This listener is called before the input changed event,
// and the `uploadInput.files` value is still null
// Wait for results from js to dart
Future.delayed(Duration(milliseconds: 500)).then((value) {
if (!changeEventTriggered) {
changeEventTriggered = true;
completer.complete(null);
}
});
}
input.onChange.listen(changeEventListener);
input.addEventListener('change', changeEventListener);
// Listen focus event for cancelled
html.window.addEventListener('focus', cancelledEventListener);
input.click();
final results = await completer.future;
if (results == null || results.isEmpty) return null;
return results.first;
}
Sources
file_picker implementation
I have a shared data that contain mobile no of customer,in my profile,that need to be filled with in textfield when i open profile page,i'm getting the data from shard preference data,when i load data to textfield it's throws error
TextEditingController mobile = TextEditingController();
void initState() {
getMobile();
}
Get data From Sharedpreference
Future<String> getMobile() async {
Future notificatinstatus = SharedPrefrence().getUserMobile();
notificatinstatus.then((data) async {
var mobile_no=data;
mobile_no.text=mobile;
return mobile;
});
}
I think is better like this:
var mobileController = TextEditingController();
getMobile() async {
Future notificatinstatus = SharedPrefrence().getUserMobile();
notificatinstatus.then((data) async {
var mobile_no=data;
setState(() {
if(mobile_no.isNotEmpty){
mobileController.text = mobile_no;
}
});
});
}
#override
void initState() {
super.initState();
getMobile();
}