How to display a Future<File> image? - flutter

My app load image from samba (instead of url) and then save thumbnail in local, so I can not use Image.network();, previouly I loaded image Uint8List from samba and then use Image.memory(bytes); to display image, it work, but I also have to combine it with Local Cache, so I save image file with flutter_cache_manager package DefaultCacheManager()(using (await DefaultCacheManager().getFileFromCache()).file), but the problem is, the DefaultCacheManager() only can return Future<File> cache instead of File, so I cannot use Image.file(cacheFile) to display image.
I have to try the FutureBuilder to solve the problem, but it cause flash in the image displaying.
I have considered to use FadeInImage and make my own ImageProvider to feed my need, but it is difficult for me to write ImageProvider.
In conclusion, I want to make something like:
Image.futureFile(Future<File>)
to use the Future cache return by DefaultCacheManager() to display a local image.If it cannot be solve(for example, a local cache file api should not return Future), I will conside to use another cache library.

Finally I end up with a custom ImageProvider, it refers to FileImage, I don't master at this so it may have problem (for example I don't test it with gif). After all, now image flashing is better than the FutureBuilder solution.
class CacheImageProvider extends ImageProvider<CacheImageProvider> {
final String fileId;//the cache id use to get cache
CacheImageProvider(this.fileId);
#override
ImageStreamCompleter load(CacheImageProvider key, DecoderCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(decode),
scale: 1.0,
debugLabel: fileId,
informationCollector: () sync* {
yield ErrorDescription('Path: $fileId');
},
);
}
Future<Codec> _loadAsync(DecoderCallback decode) async {
// the DefaultCacheManager() encapsulation, it get cache from local storage.
final Uint8List bytes = await (await CacheThumbnail.getThumbnail(fileId)).readAsBytes();
if (bytes.lengthInBytes == 0) {
// The file may become available later.
PaintingBinding.instance?.imageCache?.evict(this);
throw StateError('$fileId is empty and cannot be loaded as an image.');
}
return await decode(bytes);
}
#override
Future<CacheImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<CacheImageProvider>(this);
}
//the custom == and hashCode is need, because the ImageProvider use to get the memory cache.
#override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) return false;
bool res = other is CacheImageProvider && other.fileId == fileId;
return res;
}
#override
int get hashCode => fileId.hashCode;
#override
String toString() => '${objectRuntimeType(this, 'CacheImageProvider')}("$fileId")';
}
and use it:
Image(image: CacheImageProvider(_data[index].fileId))

// This should be in a callback or outside the build() function:
File _file = await DefaultCacheManager().getSingleFile(file);
// In the widget tree:
Image.file(_file);

Related

Flutter widget unmounting without dispose

So a piece of code I wrote needs to run on both web and mobile. And since some libraries like dart: io on the web and can't use dart:html on mobile. So in order to pick images on the web, I tried to use ImagePicker which returns an X-File, turns the X-File to bytes, and uses Image. memory().
This is the piece of code that I've been trying:
class _CanvasImageItemState extends State<CanvasImageItem> {
Uint8List? imageBytes;
XFile? file;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
// widgets
}
Widget buildPicker(BuildContext context) {
if (kDebugMode) {
print("picker loaded");
}
return InkWell(
onTap: () async {
FocusedItemModel.of(context).focus(widget.item as LocalImageItem);
if (!kIsWeb) {
// Some piece of code for mobile
} else {
await _pickImage();
}
},
child: ..some other widgets
);
}
}
_pickImage() async {
final ImagePicker _picker = ImagePicker();
file = await _picker.pickImage(source: ImageSource.gallery);
Uint8List? bytes = await file!.readAsBytes();
if (mounted) {
setState(() {});
imageBytes = bytes;
} else {
imageBytes = bytes;
}
if (kDebugMode) {
print("Checking File: ${file!.path}");
print("Checking imageBytes: ${imageBytes!.length}");
}
}
The problem is although I did not dispose() the widget or used dispose() anywhere in the project itself but I am getting
setState() called after dispose() error. So I tried checking if(mounted) before trying to update the value of imageBytes but in that case, it's taking two tries to pick the image.
As you can see I even tried to print the path of xFile and imageBytes which is getting printed properly. But I cannot set the state.
N.B: The CanvasImageItem is child of another widget called CanvasMovableItem. Any suggestion where the problem is occurring from? I've checked all possible codes that might unmount or dispose of the widget.
Am I missing something very simple?

How to get the 'bool' value from a Future<bool> into a field variable, for later use

I am using flutter_blue package for using the Bluetooth service. I want to check whether the device has Bluetooth capabilities. The method isAvailable seems to do it. However, it returns a Future<bool>, which I am tryting to get into a variable as follows:
import 'package:flutter_blue/flutter_blue.dart';
class BT_Base {
final FlutterBlue _fb = FlutterBlue.instance;
bool BTAvailable = true; // as a default placeholder
BT_Base () {
BTAvailable = _fixAvail();
}
_fixAvail () async {
return await _fb.isAvailable;
}
...
I try to get the future value from it and store into BTAvailable. Later on, I use the fixed BTAvailable field to get the appropriate Widget to be passed onto as follows:
class BTDevicePrompt extends StatelessWidget {
#override
Widget build(BuildContext context) {
BT_Base bt = BT_Base();
var btDeviceRes = bt.scan();
if(!bt.BTAvailable) return Text('Bluetooth unavailable on device...');
else if (btDeviceRes.isEmpty) return Text('No Bluetooth devices in range...');
else {
return CupertinoActionSheet(
actions: [
...
],
)
}
}
}
But I keep getting the error type 'Future<dynamic>' is not a subtype of type 'bool' at runtime. How can I use the Future properly in this situation? It is alright if the whole process just halts and waits for this part as well.
I have gone through a lot of solutions but I am not able to piece it together.
Any method marked async always returns a Future of some kind. You can give it an explicit return type like Future<bool> function() async { ... }, or if you leave it out it will infer Future<dynamic>.
In short, you can't get a bool from a Future<bool> outside of an async function (there are technically ways but almost certainly not what you want in Flutter).
This makes sense, since the whole point of a Future<bool> is that it's going to be a bool in the future. If there was some process to convert from a Future<bool> to a bool, what should it do if the future has not yet completed? Perhaps it should wait until it has completed. In that case, you're just describing the await keyword.
If, however, you want to use a Future in your UI in a Flutter application, you have a few options.
The simplest for your case will be to move it into initState():
class BTDevicePrompt extends StatefulWidget {
// stateful widget boilerplate
}
class BTDevicePromptState extends State<BTDevicePrompt> {
bool isAvailable = false;
#override
void initState() {
super.initState();
checkAvailable(); // use a helper method because initState() cannot be async
}
Future<void> checkAvailable() async {
// call an async function and wait for it to complete
bool result = await checkIfBluetoothAvailable();
setState(() => bluetoothAvailable = result); // set the local variable
}
#override
Widget build(BuildContext context) {
if (bluetoothAvailable) return Text('bluetooth available');
else return Text('bluetooth not available');
}
}

where to load model from file in flutter apps?

Suppose I store my data in a dedicated repo class like so:
class UrlEntry {
final String url;
final String title;
UrlEntry({#required this.url, this.title});
}
class UrlRepository with ChangeNotifier {
List<UrlEntry> urlEntries = new List<UrlEntry>();
// Returns the urls as a separate list. Modifyable, but doesnt change state.
List<UrlEntry> getUrls() => new List<UrlEntry>.from(urlEntries);
add(UrlEntry url) {
this.urlEntries.add(url);
print(
"url entry ${url.url} added. Now having ${urlEntries.length} entries ");
notifyListeners();
}
removeByUrl(String url) {
var beforeCount = this.urlEntries.length;
this.urlEntries.removeWhere((entry) => entry.url == url);
var afterCount = this.urlEntries.length;
if (beforeCount != afterCount) notifyListeners();
print("removed: ${beforeCount != afterCount}");
}
save() async {
final storageFile = await composeStorageFile();
print("storage file is '${storageFile.path}");
if (await storageFile.exists()) {
print("deleting existing file");
await storageFile.delete();
}
if (urlEntries == null || urlEntries.length < 1) {
print("no entries to save");
return false;
}
print(
"saving ${urlEntries.length} url entries to file $storageFile} ...");
for (var entry in urlEntries) {
await storageFile.writeAsString('${entry.url} ${entry.title}',
mode: FileMode.append);
}
}
Future<File> composeStorageFile() async {
Directory storageDir = await getApplicationDocumentsDirectory();
return File('${storageDir.path}/url_collection.lst');
}
void dispose() async {
super.dispose();
print("disposing ...");
urlEntries.clear();
this.urlEntries = null;
}
load() async {
final storageFile = await composeStorageFile();
if (!await storageFile.exists()) {
print("storage file ${storageFile.path} not existing - not loading");
return false;
}
print("loading file ${storageFile.path}");
urlEntries = List <UrlEntry> () ;
final fileLines = storageFile.readAsLinesSync() ;
for (var line in fileLines) {
var separatorIndex = line.indexOf(' ') ;
final url = line.substring(0, separatorIndex) ;
var title = line.substring(separatorIndex+1) ;
if (title == 'null') title = null ;
urlEntries.add(new UrlEntry(url: url, title: title)) ;
}
notifyListeners() ;
}
}
Above code has several issues I unfortunately donnot know how to circumvent:
most of the methods of UrlRepository are async. This is because of getApplicationDocumentsDirectory() being async. I think former is an absolute flaw but introducing semaphores here to create an artificial bottleneck would pollute the code, so I still stick to async; but call me old-fashioned - I dont like the idea having save and load operations being theoretically able to overlap each other. I mean, with getApplicationDocumentsDirectory, we're talking about a simple configurational detail that will not need much computational power to compute, nor to store, nor will it change that often and it pollutes the code with otherwise unnessecary stuff. So, Is there another way to get the results of getApplicationDocumentsDirectory() without await / async / then ?
If this is not the case - where should I put the call to save()? My first idea was to save data not every model change, but instead at the latest possible executional place, which is one of the dispose-related methods, like so:
class MyAppState extends State<MyApp> {
UrlRepository urlRepository;
...
#override
void deactivate() async {
await urlRepository.save() ;
super.deactivate();
}
Unfortunately this results in urlRepository.save() being executed only the half, no matter whether I call it in a unit test, on a avd or on a real device. Right in the middle its terminated - I checked that with printouts. I think this is because, being forced again to make a completely unrelated method async (here deactivate()), I have to accept that execution is not granted to terminate at the return command, but earlier (?). I tried to put the call to MyState.dispose() as well as to urlRepository.dispose() with the same result except I cannot make the dispose methods async and hence just call save() async and hope everything has been saved before super.dispose() kicks in,...
I thought it natural to load the repositotry state inside of initState(), but I want to make sure that either the load has completed before creating widgets (ie calling the builder), or will be loaded after all widgets have already been in place so the model change will trigger rebuild. Since load() has to be async for known reasons and initState is not, I cannot assure even one of above cases and stick with urlRepository.load() and hope the best. So, where to put the call to urlRepository.load() ?
First: You have to use async/await methods because you don't know what the user's device may be doing while running your application, so even though the device might be performing a process that "doesn't need much power computational" that process may take a little longer than expected.
Second: Do not trust in deactivate() and dispose() functions, the user could kill your app and it would never do the save process. I'm not really sure how to automate this process. I suggest you do it manually somewhere in your code.
Third: Don't use initState() to load your data, you should use a FutureBuilder in which the future parameter is your urlRepository.load() function, while its loading you can show a CircularProgressIndicator() and if it has data you show your widget.
Example:
#override
Widget build() {
return FutureBuilder(
future: urlRepository.load() // without await keyword
builder: (context, snapshot) {
if(!snapshot.hasData)
return CircularProgressIndicator();
return YourWidget(); // if snapshot.hasData is true the process has finished
}
);
}
Psdt: It might be useful if urlRepository.load() would return something like a bool. Doing this you could show a widget if snapshot.data is true or another widget if snapshot.data is false.

How to upload pic from assets?

I am using below code to upload the pic from gallery in flutter, if in case the pic is not picked up from the gallery I want the pic from the assets to be uploaded to the firebase storage, for that avatarImageFile should be equivalent to the image file from the assets.
How Can I achieve that?
Future getImage() async {
print("get image");
PickedFile image = await _picker.getImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
final File file = File(image.path);
avatarImageFile = file;
isLoading = true;
});
}
else{
//if image is null then the image from the assets should be made picked into `avatarImageFile `
}
}
In Flutter you can load your assets in two ways:
Using rootBundle.loadString("assets/my_file.json") to load text files
Using rootBundle.load("assets/something.png") to load any kind of file (images, pdf or any other kind of binary).
Note that load also works with .json files but in general loadString is a better choice when it comes to retrieving text. For more info, read the doc.
avatarImageFile = await rootBundle.load("assets/a/b/c.png");
Do not use rootBundle when you're inside widgets: instead, prefer using DefaultAssetBundle:
class MyWidget extends StatelessWidget {
const MyWidget();
Future<String> loadConfig(BuildContext context) async =>
await DefaultAssetBundle
.of(context)
.loadString("myassets/some_cfg.json");
#override
Widget build(BuildContext context) {...}
}
Again, do the above when you're inside widgets. In any other case, go for rootBundle.

Flutter: get default context? or load assets without context?

I'm trying to load a json file in a class extending SearchDelegate to search through its content.
I have a method to load this file:
Future<void> loadCountryData() async {
try {
String data = await DefaultAssetBundle
.of(context)
.loadString("assets/data/countries.json");
_countries = json.decode(data);
} catch (e) {
print(e);
}
}
Unfortunately this requires a Buildcontext (context) that seems only to be available in the SearchDelegate build methods (like buildActions, buildLeadings, etc), but no outside like for example in the constructor.
https://docs.flutter.io/flutter/material/SearchDelegate-class.html
As the #override xy build methods in SearchDelegate are called with every change in the search field, I would load my file over and over again, which is of course not ideal.
I want to load my file once at the beginning only.
Is there a way to get some sort of get default context that I could use for example in the constructor of SearchDelegate. Like in android (if I remmeber correctly)?
Or can I load an assets file without .of(context)?
Description of function
/// The bundle from the closest instance of this class that encloses
/// the given context.
///
/// If there is no [DefaultAssetBundle] ancestor widget in the tree
/// at the given context, then this will return the [rootBundle].
///
/// Typical usage is as follows:
///
/// ```dart
/// AssetBundle bundle = DefaultAssetBundle.of(context);
/// ```
static AssetBundle of(BuildContext context) {
final DefaultAssetBundle result = context.dependOnInheritedWidgetOfExactType<DefaultAssetBundle>();
return result?.bundle ?? rootBundle;
}
So you may simply use rootBundle instead of DefaultAssetBundle.of(context) for working with assets without context.
There is an option to get builtin AssetBundle without specifying a reference to BuildContext. Here is an example of how it could be done:
import 'package:flutter/services.dart'; // is required
Future<void> loadCountryData() async {
try {
// we can access builtin asset bundle with rootBundle
final data = await rootBundle.loadString("assets/data/countries.json");
_countries = json.decode(data);
} catch (e) {
print(e);
}
}
As DefaultAssetBundle is based on an InheritedWidget you will always need to pass a context.
of just looks up the widget tree based on a BuildContext until it finds a DefaultAssetBundle widget. This means that you cannot retrieve a DefaultAssetBundle object without a BuildContext.
You will need to pass a BuildContext to your method. I could imagine a situation like the following:
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: loadCountryData(context: context),
builder: (BuildContext context, AsyncSnapshot<JSON> jsonData) {
if (!jsonData.hasData) {
return Text('not loaded');
}
return Text('loaded'); // here you want to process your data
},
);
}
/// I am not sure what your decode returns, so I just called it JSON
/// I thought it would make more sense to return the JSON to use it in build
Future<JSON> loadCountryData({BuildContext context}) async {
try {
String data = await DefaultAssetBundle
.of(context)
.loadString("assets/data/countries.json");
return json.decode(data);
} catch(e) {
print(e);
return JSON.empty(); // imagine this exists
}
}
As you can see I passed the BuildContext from the build method. The FutureBuilder also allows to process the data in the build tree directly.
You can give the BuildContext as a parameter through to loadCountryData(BuildContext context).