How to refresh widget? - flutter

I want to "refresh" my widget after i upload a file.
new Container(
child: _profileAvatar(),
width: 150.0,
height: 150.0,
),
Widget _profileAvatar(){
if(!hasProfilImage){
return CircleAvatar(
radius: 30.0,
backgroundImage:
new ExactAssetImage('assets/images/default_profile.png'),
backgroundColor: Colors.transparent,
);
}else {
return CircleAvatar(
radius: 30.0,
backgroundImage:
NetworkImage( "${Configuration.url}assets/profileImage/${userProfilImage}"),
backgroundColor: Colors.transparent,
);
}
}
and here is how i upload a file
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
if(image != null) {
_image = image;
hasProfilImage = true;
_upload();
}
});
}
for now after the upload process finished, i need to re open this "page" to see the new image.
How can i show the new image immediately after the upload process finish?
thanks in advance
Here is what i try so far , but still no help
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
if(image != null) {
_image = image;
_upload().then((isSuccess){
setState(() {
hasProfilImage = true;
});
});
}
});
}
here is my full script https://pastebin.com/59UfYa2P

As you are working with flutter you can use StatefulWidget which has a setState method. whenever you want to update the data put that data after processing. It will automatically update at its position.
setState(() {});
If you have the API call or function in another widget you can use callback to update the widget.
If you want to update the previous widget after the processing the on the next widget Here is the callback
_navigateAndDisplayDriverSelection(
BuildContext context, String userId, String driveId) async {
final driverModal = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Drivers(userId: userId, driveId: driveId)),
);
setState(() {});
}

Your Upload function probably returns a Future with some value whether the image is successfully uploaded or not.
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
if(image != null) {
_image = image;
_upload().then((isSuccess){
// once upload is completed then, call setState and it will rebuild the widget tree and update the view.
setState(() {
hasProfilImage = true;
});
});
}
}

If you don't want to pass down your setState method through your widget tree, you can also use ScopedModels.
This page has a brief example of how to implement a scopedmodel:
https://pub.dev/packages/scoped_model
Just know that every time you call notifylisteners(), everything underneath ScopedModelDescendant gets rebuild.
The issue you are facing is one of state-management, and there are also alternative solutions for this problem: https://flutter.dev/docs/development/data-and-backend/state-mgmt

Related

Expected a value of type 'ImageProvider<Object>', but got one of type 'Image'

I'm building a web app with flutter and I want an option to change the profile picture. To pick image from gallery I use the image_picker_web 2.1.1 package.
class ImagePickerService {
late Image? img = Image.asset('whatever'); //has to be initialised
Future<void> pickImage() async {
img = await ImagePickerWeb.getImageAsWidget();
}
}
I didn't set the type of pickImage() to Future<Image?> because then I'd have to convert from Future. This asset('whatever'), although not very elegant, doesn't cause any problems because before _fileLoaded is set to true while picking an image, I display username's initial letter as avatar. Without it I was getting an error of no initialisation.
Relevant snippets from settings page class:
late Image? avatar;
bool _fileLoaded = false;
final ImagePickerService _ips = ImagePickerService();
Center(
child: displayAvatar(),
),
TextButton.icon(
onPressed: () {
setState(() {
_ips.pickImage();
avatar = _ips.img;
_fileLoaded = true;
});
},
icon: const Icon(Icons.edit),
label: const Text('Change avatar'),
),
Widget displayAvatar() {
if (_fileLoaded) {
return CircleAvatar(
backgroundImage: avatar as ImageProvider, radius: 50.0);
} else...
I searched for similar problems, but didn't find any answer except for adding as ImageProvider, which in my case removes the error from IDE and lets me run the project, but the error appears later on the red screen when I press the button to change avatar, even though the type should be correct. Please, do you know any solution?
ImageProvider is an abstract class. Classes that implement ImageProvider are AssetImage(""), NetworkImage("URL"), FileImage() etc. I believe FileImage should be well suited for your case.
I'm posting the solution, in case someone has the same problem.
I switched back to the image_picker package, it supports web too, you just have to use it a bit differently:
class ImagePickerService {
final ImagePicker _picker = ImagePicker();
Uint8List picked = Uint8List(8);
Future<void> pickImage() async {
XFile? image = await _picker.pickImage(source: ImageSource.gallery);
if (image != null) picked = await image.readAsBytes();
}
}
ClipOval(
child: Image.memory(
avatar,
fit: BoxFit.cover,
width: 100.0,
height: 100.0,
),
)
avatar is assigned to _ips.picked, so it's of Uint8List type.

How to make showDIalog wait for a function to finish first before building widget

So I'm trying to create a qr scanner feature, I manage to do all the function and functionality for the code, but when my code try to fetch the data the widget build run first resulting some data is null. How can I make the showDialog widget to wait for my function to be done first before moving on in building the data ?
Here's my code:
void getQRData() async {
// Get User Data from ID
print('qrcode: $qrcode');
// Take the UserData[Number] to get user short data
data = await UserDatabase.getUserShortData(
userId: qrcode,
phoneNumber: null,
);
print('data is : $data');
await Future.forEach(
Provider.of<ContactDataProvider>(context, listen: false)
.getContactConnection
.keys, (key) async {
connectedContactData =
Provider.of<ContactDataProvider>(context, listen: false)
.getContactConnection[key];
print(connectedContactData);
if (connectedContactData['status'] == 'connected' ||
connectedContactData['status'] == 'sent_req') {
filteredId.add(connectedContactData['contact_uid']);
print('filteredId: ${filteredId}');
}
});
}
Container(
width: 250,
height: 250,
child: ScanView(
controller: scanController,
scanAreaScale: 0.8,
scanLineColor: Colors.red,
onCapture: (photoData) {
qrcode = photoData;
print('qrcode: $qrcode');
getQRData();
print(data);
//ShowDialog Widget Code
},
),
),
On the code above, my showDialog widget code run first before the getQRData() function is done. Resulting the widget is always error because the data is still null. Like what I just print
Firstly, convert getQRData method from void to future.
Future<void> getQRData() async {...}
Now on onCapture
onCapture: (photoData) async {
qrcode = photoData;
await getQRData();
await showDialog(...);
},
More about dart async-await

Correct way to work with future objects within multiple Future builder in Flutter

For example I have a late future object with news list within a StatefulWidget
late Future<List<News>> loadedList;
I need to use this loadedNewsList outside of Future.builder in some methods like that (this code will fail as far loadedList is not inited yet)
void markLoadedAsRead() async {
String _time = DateTime.now().toString();
for (var el in loadedList!) {
if(el.readAt == null){
el.readAt = _time;
final _n = await DatabaseHelper.instance.findNews(el.uuid);
if(_n == null) {
await DatabaseHelper.instance.insertNews(el);
} else {
await DatabaseHelper.instance.updateNews(el);
}
}
}
setState((){});
}
And also I want some inerface Future.builders to be aware loadedList is loaded like that. Appbar example that is shown only when loadedList is completed.
actions: <Widget>[
FutureBuilder<List<News>>(
future: loadedList, // a Future<String> or null
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) developer.log(snapshot.error.toString());
if (!snapshot.hasData) return const SizedBox.shrink();
return IconButton(
icon: const Icon(Icons.filter_list_alt),
onPressed: () async {
developer.log('FEED smooth scroll news to top before filter click');
_scrollToTop();
// disposeAd();
await Navigator.push(context, MaterialPageRoute(builder: (BuildContext context) => const FilterPage()));
// getBanner();
developer.log('FEED call news after filter page visit');
// await fetchNews(false);
// setState(() {});
},
);
}
)
]
So, what is the correct way to handle that in Flutter?

call one asynchronous function when the first one runs flutter

I have two asynchronous functions, one returns a popup, the other makes a permission request. I call them in the init method. But they are called simultaneously, i.e. the first window appears and immediately the second. How do I fix this?
class _MyAppState extends State<MyApp> {
final keyIsFirstLoaded = 'is_first_loaded';
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final context = MyApp.navKey.currentState.overlay.context;
await showDialogIfFirstLoaded(context);
await initPlatformState();
});
}
showDialogIfFirstLoaded(BuildContext context, prefs) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isFirstLoaded = prefs.getBool(keyIsFirstLoaded);
if (isFirstLoaded == null) {
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return new AlertDialog(
// title: new Text("title"),
content: new Text("//"),
actions: <Widget>[
new FlatButton(
child: new Text(".."),
onPressed: () {
Navigator.of(context).pop();
prefs.setBool(keyIsFirstLoaded, false);
},
),
],
);
},
);
}
}
initPlatformState() async {
print('Initializing...');
await BackgroundLocator.initialize();
print('Initialization done');
final _isRunning = await BackgroundLocator.isRegisterLocationUpdate();
setState(() {
isRunning = _isRunning;
});
onStart();
print('Running ${isRunning.toString()}');
}
add return to showDialog statement, you're not returning a Future so await isn't doing anything
Personal advice: always specify return types, cause if you don't, you get dynamic return type. If you do specify it, the IDE/dart analysis server will help you with problems such as this one.

ImagePicker, how to set image again?

Minimal Code:
File _file;
Future<void> _pickImage() async {
final image = await ImagePicker.pickImage(source: ImageSource.camera);
if (image != null) {
final file = File("${(await getApplicationDocumentsDirectory()).path}/image.png");
await file.writeAsBytes(await image.readAsBytes());
setState(() => _file = file); // `_file = image` works though
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(child: Icon(Icons.camera_alt), onPressed: _pickImage),
body: _file == null ? Container() : Image.file(_file),
);
}
Watch video
As you can see, once I pick the image, it works, but on picking it second time, it doesn't work and I also don't run into any error. Can anyone please help?
you need 3 things:
first you have to use ImageProvider and its evict() method:
var image = FileImage(File('someImage.jpg'));
then you need Image widget that uses above ImageProvider and also assigns a unique key in order to be "different" each time build() method is called:
child: Image(
image: image,
key: UniqueKey(),
),
and finally after you overwrite someImage.jpg you have to call evict() method:
// part of your _pickImage() method
// here someImage.jpg contains updated content
image.evict();
setState(() {});
UPDATE: actually you dont need var image = FileImage(File('someImage.jpg')); - you can use it directly inside Image widget as image: FileImage(File('someImage.jpg')) and call FileImage(File('someImage.jpg')).evict() after your image is ovewritten