1

I have created an example where I can add element to flutter list view dynamically. Each list view element is having button and text. when i add new element incorrect text is shown. I am adding incremental data and expected that each item will have value 1 , 2 , 3 , 4 .... But value of each item is 1 only. Also state of button pressed on a particular item is not maintained , after I scroll the list then only correct values are shown.

Can we not use stateful widgets inside listView? . Issue and Code is as follows

Recording

 import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  List<String> europeanCountries = [];
  int _counter = 0;
  int _perPage = 50;
  ScrollController _myScrollController = ScrollController();

  void _incrementCounter() async {
    const ThreeSec = const Duration(seconds: 1);
    this._counter++;
    europeanCountries.insert(0, this._counter.toString());
    print(europeanCountries);

    setState(() {});
  }

  void getMoreData() {
    print('adding More Product ');

    europeanCountries.add("New Product");

    setState(() {});
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    _myScrollController.addListener(() {
      double maxscroll = _myScrollController.position.maxScrollExtent;
      double currentScroll = _myScrollController.position.pixels;
      double delta = MediaQuery.of(context).size.height * 0.25;
      print("mac Scroll Controller - " + maxscroll.toString());
      print("Current  Scroll Controller - " + currentScroll.toString());
      print("delta  Scroll Controller - " + delta.toString());

      if ((maxscroll - currentScroll) < delta) {
        getMoreData();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _myListView(context),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Widget _myListView(BuildContext context) {
    // backing data

    return Container(
      child: europeanCountries.length == 0
          ? Center(
              child: Text('No Product to Display'),
            )
          : ListView.builder(
              controller: _myScrollController,
              itemCount: europeanCountries.length,
              reverse: false,
              itemBuilder: (context, index) {
                return myContainer(
                  mytext: europeanCountries[index],
                );
              },
            ),
    );
  }
}

class myContainer extends StatefulWidget {
  final String mytext;

  const myContainer({Key key, this.mytext}) : super(key: key);

  @override
  _myContainerState createState() => _myContainerState(mytext);
}

class _myContainerState extends State<myContainer> {
  final String mytext;
  String _buttontext = "inprogress";
  Color myClr = Colors.blue;

  _changeText() {
    setState(() {
      _buttontext = "Done";
      myClr = Colors.green;
    });
  }

  _myContainerState(this.mytext);
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.blue[700]),
        shape: BoxShape.rectangle,
        borderRadius: BorderRadius.all(Radius.circular(8)),
      ),
      margin: EdgeInsets.all(20),
      child: Column(
        children: <Widget>[
          Text(mytext),
          RaisedButton(
            child: Text(_buttontext),
            onPressed: _changeText,
            color: myClr,
            textColor: Colors.white,
            padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
            splashColor: Colors.black,
          )
        ],
      ),
    );
  }
}
1
  • This looks like a key issue. I'd checkout Emily Fortuna's great article about the matter. It basically boils down to adding a unique key value on list items. Also, yes, if you use the builder constructor of the ListView, the items will get built on demand and not hold state I think. Commented Dec 3, 2019 at 19:06

1 Answer 1

1

Edit update full code mentioned in comments
use Class to keep button color

code snippet

class EuropeanCountries {
  String myText;
  String myButtonText;
  Color myColor;

  EuropeanCountries({
    this.myText,
    this.myButtonText,
    this.myColor,
  });

}

working demo for edit

enter image description here

full code for edit

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class EuropeanCountries {
  String myText;
  String myButtonText;
  Color myColor;

  EuropeanCountries({
    this.myText,
    this.myButtonText,
    this.myColor,
  });

}


class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

List<EuropeanCountries> europeanCountries = [];

class _MyHomePageState extends State<MyHomePage> {

  int _counter = 0;
  int _perPage = 50;
  ScrollController _myScrollController = ScrollController();

  void _incrementCounter() async {
    const ThreeSec = const Duration(seconds: 1);
    this._counter++;
    europeanCountries.insert(0, EuropeanCountries(myText:this._counter.toString(), myButtonText: "inprogress", myColor: Colors.blue));
    print(europeanCountries);

    setState(() {});
  }

  void getMoreData() {
    print('adding More Product ');

    europeanCountries.add(EuropeanCountries(myText:this._counter.toString(), myButtonText: "inprogress", myColor: Colors.blue));
    //europeanCountries.insert(0, EuropeanCountries(myText:this._counter.toString(), myButtonText: "", myColor: Colors.blue));
    setState(() {});
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    _myScrollController.addListener(() {
      double maxscroll = _myScrollController.position.maxScrollExtent;
      double currentScroll = _myScrollController.position.pixels;
      double delta = MediaQuery.of(context).size.height * 0.25;
      print("mac Scroll Controller - " + maxscroll.toString());
      print("Current  Scroll Controller - " + currentScroll.toString());
      print("delta  Scroll Controller - " + delta.toString());

      if ((maxscroll - currentScroll) < delta) {
        getMoreData();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _myListView(context),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Widget _myListView(BuildContext context) {
    // backing data

    return Container(
      child: europeanCountries.length == 0
          ? Center(
              child: Text('No Product to Display'),
            )
          : ListView.builder(
              controller: _myScrollController,
              itemCount: europeanCountries.length,
              reverse: false,
              itemBuilder: (context, index) {
                return myContainer(
                    index : index
                );
              },
            ),
    );
  }
}

class myContainer extends StatefulWidget {
  final int index;
   const myContainer({Key key, this.index}) : super(key: key);

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

class _myContainerState extends State<myContainer> {
  //final String mytext;
  //String _buttontext = "inprogress";
  //Color myClr = Colors.blue;

  _changeText() {
    setState(() {
      europeanCountries[widget.index].myButtonText = "Done";
      europeanCountries[widget.index].myColor= Colors.green;
    });
  }

  //_myContainerState(this.mytext);
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.blue[700]),
        shape: BoxShape.rectangle,
        borderRadius: BorderRadius.all(Radius.circular(8)),
      ),
      margin: EdgeInsets.all(20),
      child: Column(
        children: <Widget>[
          Text(europeanCountries[widget.index].myText),
          RaisedButton(
            child: Text(europeanCountries[widget.index].myButtonText),
            onPressed: _changeText,
            color: europeanCountries[widget.index].myColor,
            textColor: Colors.white,
            padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
            splashColor: Colors.black,
          )
        ],
      ),
    );
  }
}

You can copy paste run full code below
myContainer extends StatefulWidget did not use in correct way.

Step 1 : _myContainerState(mytext); remove mytext
Step 2 : remark //final String mytext;
Step 3 : remark //_myContainerState(this.mytext);
Step 4 : Text(widget.mytext) add widget.

working demo

enter image description here

full code

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  List<String> europeanCountries = [];
  int _counter = 0;
  int _perPage = 50;
  ScrollController _myScrollController = ScrollController();

  void _incrementCounter() async {
    const ThreeSec = const Duration(seconds: 1);
    this._counter++;
    europeanCountries.insert(0, this._counter.toString());
    print(europeanCountries);

    setState(() {});
  }

  void getMoreData() {
    print('adding More Product ');

    europeanCountries.add("New Product");

    setState(() {});
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    _myScrollController.addListener(() {
      double maxscroll = _myScrollController.position.maxScrollExtent;
      double currentScroll = _myScrollController.position.pixels;
      double delta = MediaQuery.of(context).size.height * 0.25;
      print("mac Scroll Controller - " + maxscroll.toString());
      print("Current  Scroll Controller - " + currentScroll.toString());
      print("delta  Scroll Controller - " + delta.toString());

      if ((maxscroll - currentScroll) < delta) {
        getMoreData();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _myListView(context),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Widget _myListView(BuildContext context) {
    // backing data

    return Container(
      child: europeanCountries.length == 0
          ? Center(
              child: Text('No Product to Display'),
            )
          : ListView.builder(
              controller: _myScrollController,
              itemCount: europeanCountries.length,
              reverse: false,
              itemBuilder: (context, index) {
                return myContainer(
                  mytext: europeanCountries[index],
                );
              },
            ),
    );
  }
}

class myContainer extends StatefulWidget {
  final String mytext;

  const myContainer({Key key, this.mytext}) : super(key: key);

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

class _myContainerState extends State<myContainer> {
  //final String mytext;
  String _buttontext = "inprogress";
  Color myClr = Colors.blue;

  _changeText() {
    setState(() {
      _buttontext = "Done";
      myClr = Colors.green;
    });
  }

  //_myContainerState(this.mytext);
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.blue[700]),
        shape: BoxShape.rectangle,
        borderRadius: BorderRadius.all(Radius.circular(8)),
      ),
      margin: EdgeInsets.all(20),
      child: Column(
        children: <Widget>[
          Text(widget.mytext),
          RaisedButton(
            child: Text(_buttontext),
            onPressed: _changeText,
            color: myClr,
            textColor: Colors.white,
            padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
            splashColor: Colors.black,
          )
        ],
      ),
    );
  }
}
Sign up to request clarification or add additional context in comments.

4 Comments

After adding the code , one issue remains. (please refer video imgur.com/mDNKHQG )... I added list item 1 then clicked on button. button changed from blue to green .. then added list item 2 . now list item 2 shows button green . list item 2 should show blue button and list item 1 should show green button since it was pressed.
ListView just build your data. You have to change europeanCountries to List<yourCalss> to keep each text and Color. only List<String> is not enough
,Just a minor query, How are you embedding the video in answer itself. I have to upload video somewhere else because of size.
you can use screentogif.com screen to gif . it's a gif

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.