Ajay
Ajay

Reputation: 16290

How to maintain the state of widget in ListView?

I have a list of clickable widgets[i.e MarkWidget] when the widget is clicked the state of widget is changed. But when the list is scrolled to the bottom and scrolled back to the top all widget's state is reset to default.

How do I stop/force flutter to not redraw the existing widget in the list after scroll?

For Example : if I click on ITEM 1 is color changes from green to red but if scroll to the bottom and scroll back to top the ITEM 1 color changes back to green. I need the ITEM 1 color to be red if it is clicked irrespective of scrolling.

DEMO

Here is code :

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(),
      theme: ThemeData(
        platform: TargetPlatform.android,
      ),
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  List<Widget> _widgetList = List.generate(
    30,
    (index) => MarkWidget(
          key: Key('ITEM $index'),
          title: 'ITEM $index',
        ),
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("My List"),
      ),
      body: ListView.builder(
        key: new Key("my_list"), //new

        itemBuilder: (BuildContext context, int index) {
          return _widgetList[index];
        },
        itemCount: _widgetList.length,
      ),
    );
  }
}

class MarkWidget extends StatefulWidget {
  final String title;

  const MarkWidget({Key key, this.title}) : super(key: key);

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

class _MarkWidgetState extends State<MarkWidget> {
  bool _checked = false;

  @override
  Widget build(BuildContext context) {
    return FlatButton(
      onPressed: () {
        setState(() {
          _checked = !_checked;
        });
      },
      child: Container(
        padding: EdgeInsets.all(10.0),
        margin: EdgeInsets.all(10.0),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.all(
            Radius.circular(5.0),
          ),
          color: _checked ? Colors.red : Colors.green,
        ),
        child: Text(
          "${widget.title}",
          style: TextStyle(
            color: Colors.white,
            decoration: _checked ? TextDecoration.lineThrough : TextDecoration.none,
          ),
        ),
      ),
    );
  }
}

Upvotes: 35

Views: 11417

Answers (4)

Patrick Waweru
Patrick Waweru

Reputation: 206

Sometimes just increase the cacheExtent value

 ListView(
  cacheExtent: 2000,
 ...

Upvotes: 1

user20018055
user20018055

Reputation: 11

Now, how about PageStorageKey?

    ListView.builder(
    key: const PageStorageKey<String>("my_list"), //must be unique

    itemBuilder: (BuildContext context, int index) {
      return _widgetList[index];
    },
    itemCount: _widgetList.length,
  ),

The PageStorageKey will preserve the state of each item when scrolling up and down.

look the PageStorage class at official Flutter website

Upvotes: 1

Ivan Yoed
Ivan Yoed

Reputation: 4395

If you don't explicitly need a ListView.builder, an option would be to change your ListView for a Column and wrap it inside a SingleChildScrollView. The Column will preserve the state of your widgets (because it does not load them lazily, like the ListView does). That way you can avoid having to make your widgets make use of AutomaticKeepAliveClientMixin, which is also correct, but a different approach. It just depends on what you need.

Upvotes: 5

diegoveloper
diegoveloper

Reputation: 103351

Add the AutomaticKeepAliveClientMixin mixin to your State of your StatefulWidget (Item widget), override the wantKeepAlive method and return true.

     class _MarkWidgetState extends State<MarkWidget> with AutomaticKeepAliveClientMixin{
       ...

         
    @override
    Widget build(BuildContext context) {
      // call this method.
      super.build(context);
      ...
    }

     @override
       bool get wantKeepAlive => true;
     }

More info here: https://docs.flutter.io/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html

Upvotes: 64

Related Questions