Search code examples
dartfluttersetstate

Flutter setState is reverting


When I setState and add an image to the _images array, it appears to have added, but then it quickly reverts:

enter image description here

This form is loosely following Brian Egan's redux architecture example:

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

class Note {
  final String comments;
  final List<String> images;

  Note({
    this.comments,
    this.images,
  });
}

class AddNote extends StatefulWidget {
  final Note note;
  final bool isEditing;

  AddNote({
    this.note,
    this.isEditing,
  });

  @override
  _AddNoteState createState() => _AddNoteState();
}

class _AddNoteState extends State<AddNote> {
  static final _scaffoldKey = GlobalKey<ScaffoldState>();
  static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();

  List<String> _images;
  String _comments;

  Note get _note => widget.note;
  bool get _isEditing => widget.isEditing;

  @override
  Widget build(BuildContext context) {
    _images = _note.images;
    _comments = _note.comments;

    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: Text(
          _isEditing ? "Edit Note" : "Create Note",
        ),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              _photoPickerField(),
              _notesField(),
            ],
          ),
        ),
      ),
    );
  }

  Widget _photoPickerField() {
    return GestureDetector(
      onTap: _selectPicture,
      child: Row(
        children: <Widget>[
          Container(
            decoration: BoxDecoration(
              border: Border.all(color: Colors.grey, width: 1,),
              borderRadius: BorderRadius.all(const Radius.circular(10)),
            ),
            child: SizedBox(child: Icon(Icons.camera_alt), width: 110, height: 110,)
          ),
        ] + _imagesRowItems(),
      ),
    );
  }

  List<Widget> _imagesRowItems() {
    return _images.map((image) {
      return SizedBox(
        height: 110,
        width: 110,
        child: Image.file(File(image), height: 110, width: 110, fit: BoxFit.cover),
      );
    }).toList();
  }

  Future _selectPicture() async {
    return ImagePicker.pickImage(source: ImageSource.gallery)
      .then((file) {
        setState(() {
          _images.add(file.path);
        });
    });
  }

  Widget _notesField() {
    return TextFormField(
      maxLines: 2,
      keyboardType: TextInputType.multiline,
      initialValue: _comments,
      onSaved: (String value) => _comments = value,
    );
  }
}

Note that the comments field keeps its state without issue. How can I add to the images array in a way that will maintain its new state?


Solution

  • Your problem is that you're setting variables inside the build() method of the Widget state, but the build method is called every time you call setState() because your variables have changed, so it resets the images and comments.

    To fix it, you should initialize your variables in the initState() method, like this:

    class _AddNoteState extends State<AddNote> {
      ...
      @override
      void initState() {
        super.initState();
        _images = _note.images;
        _comments = _note.comments;
      }
    }
    

    And remove them from the build() method.