Duncanista
Duncanista

Reputation: 123

Flutter Image Widget won't update on change state

I am creating an Image Editor application using sliders, something like Instagram, and I am using library image/image.dart. The problem is that once you move the slider it updates the image but just that time, if you move it again, it won't update.

I have set everything as expected, setState() functions as flutter asks, but I don't know why it won't update again.

import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';

import 'package:image/image.dart' as br;
import 'package:path_provider/path_provider.dart';
import 'package:image_picker/image_picker.dart';

class ImageManager extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
    return _ImageManagerState();
  }
}

class _ImageManagerState extends State<ImageManager> {
  File imageFile;
  br.Image image;
  Image _imageWidget;

  Future<String> get _localPath async {
    final directory = await getApplicationDocumentsDirectory();

    return directory.path;
  }

  // get tmp file
  Future<File> get _localFile async {
    final path = await _localPath;
    return File('$path/tmp.jpg');
  }

  // pick image on button click
  Future<void> _pickImage(ImageSource source) async{
    File selectedFile = await ImagePicker.pickImage(source: source);
    br.Image selectedImage;
    if (selectedFile != null){
      selectedImage = br.decodeImage(selectedFile.readAsBytesSync());
      br.grayscale(selectedImage);

      selectedFile.writeAsBytesSync(br.encodeJpg(selectedImage));
    }

    setState((){
      image = selectedImage;
      imageFile = selectedFile;
      _imageWidget = Image.file(imageFile);
    });
  }

  // MAIN PROBLEM, UPDATING THE CONTRAST WILL ONLY DO IT ONCE
  Future<void> updateContrast(value) async{
    File contrastFile = imageFile;
    br.Image contrast = br.decodeImage(contrastFile.readAsBytesSync());
    contrast = br.adjustColor(contrast, contrast: value);
    contrastFile.writeAsBytesSync(br.encodeJpg(contrast));

    // Save the thumbnail as a jpg.
    File path = await _localFile;
    path.writeAsBytesSync(br.encodeJpg(contrast));
    setState(() {
      image = contrast;
      imageFile = contrastFile;
      if(path != null){
        _imageWidget = Image.file(path);
        print(value);
      }

    });
  }

  // 
  Widget _buildImage(BuildContext context){
    return Column(
      children: [
        Container(
          width: MediaQuery.of(context).size.width,
          height: MediaQuery.of(context).size.width,
          child: _imageWidget,
        ),
        Column(
          children: [
            Container(
              padding: const EdgeInsets.only(left: 8, right: 8),
              child: Column(
                children: <Widget>[
                  // contrast
                  Text("Contraste"),
                  Padding(
                    padding: const EdgeInsets.only(bottom: 4.0, top: 0.0),
                    child: Container(
                      child: Slider(
                        min: 0.0,
                        max: 1.0,
                        divisions: 100,
                        value: _contrast,
                        activeColor: Colors.blue[500],
                        inactiveColor: Colors.blue[50],
                        label: "${(_contrast *100).round()}",
                        onChanged: (value) async{
                          changeContrast(value);
                        },
                        onChangeEnd: (value) async{
                          updateContrast(value);
                        },
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ]
    );
  }


I expect the image to update every time the slider is changed.

Upvotes: 7

Views: 8365

Answers (5)

Alaindeseine
Alaindeseine

Reputation: 4413

As of April 2024, i get in same problem. In my case this appears when i have many images named xxxx_01.jpg, xxxx_02.jpg, xxxx_03.jpg, etc.

if I delete xxxx_02.jpg and rename xxxx_03.jpg to xxxx_02.jpg then old xxxx_02.jpg is displayed instead of new xxx_02.jpg. Clearly a cache problem.

For me the solution is to issue this :

imageCache.clear();
imageCache.clearLiveImages();

Explanation for first line is:

Evicts all pending and keepAlive entries from the cache.

This is useful if, for instance, the root asset bundle has been updated and therefore new images must be obtained.

Images which have not finished loading yet will not be removed from the cache, and when they complete they will be inserted as normal.

This method does not clear live references to images, since clearing those would not reduce memory pressure. Such images still have listeners in the application code, and will still remain resident in memory.

For the second line, explanation is:

Clears any live references to images in this cache.

An image is considered live if its [ImageStreamCompleter] has never hit zero listeners after adding at least one listener. The [ImageStreamCompleter.addOnLastListenerRemovedCallback] is used to determine when this has happened.

This is called after a hot reload to evict any stale references to image data for assets that have changed. Calling this method does not relieve memory pressure, since the live image caching only tracks image instances that are also being held by at least one other object.

Upvotes: 0

ghchoi
ghchoi

Reputation: 5156

For me, I just added UniqueKey to ListView.

            child: ListView(
              key: UniqueKey(),

Upvotes: 1

Mudasir Habib
Mudasir Habib

Reputation: 848

Use Image.memory instaead Image.file, A line from my code

Image.memory(
          File(widget.imagePath).readAsBytesSync(),),

Upvotes: 2

Anupam
Anupam

Reputation: 538

imageCache.clear() will do the job.

I was also not able to reload the image to save locally on the screen. From debugging, I observed the old image is in fact deleted, and new image is copied there, but nothing was changing on the screen. Following is the code you need.

So, in the body of Scaffold, I have made a FutureBuilder that calls another function reload(), which then loads the file and return the image.

FutureBuilder(
future: reload(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
    if(snapshot.connectionState == ConnectionState.done){
        return snapshot.data;
    }  
    else{
        return CircularProgressIndicator();
    }
},
),

Here is the reload() function:

reload() async {
String path = "path of your image";
File profileImage = File("$path/name.jpg");

if(profileImage.existsSync() == false){
  return Text("File Not Found");
}
else{
  imageCache.clear();
  return Image.file(profileImage);
}
}

I checked Flutter's github, and jamesncl has already suggested this.

Upvotes: 13

Duncanista
Duncanista

Reputation: 123

I solved the issue, which is pretty weird to me, but if someone can explain it, I would appreciate it.

Future<void> adjustImage() async{
  File toAdjustFile = imageFile;
  br.Image toAdjust = br.decodeImage(toAdjustFile.readAsBytesSync());
  toAdjust = br.adjustColor(toAdjust, contrast: _contrast, brightness: _brightness, exposure: _exposure);
  setState(() {
    _imageWidget = Image.memory(br.encodeJpg(toAdjust));
  });
}

I refactored my function and set the widget to another constructor, Image.memory().

Upvotes: 3

Related Questions