11

I am creating the List of Cards according to the number of toDoId.

toDoController.toDo() is like

toDo = [q1, r4, g4, d4].obs;

And, this is my ListView.builder()

  Obx(() {
         List _todo = toDoController.toDo();

         return ListView.builder(
         shrinkWrap: true,
         scrollDirection: Axis.horizontal,
         itemCount: _todo.length,
         itemBuilder: (BuildContext context, int i) {
                           
         var _loading = true;
         var _title = 'loading';
                          
         getTodoInfo() async {
         _title = await toDoController
                .getTodoInfo(
                     _todo[i]
                 );
         _loading = false;
         print(_title); // 'Clean!' <--- returns correct title
         }

         getTodoInfo();

         return Container(
           height: 150,
           width: 150,
           child: _loading
           ? Text(
             _title,
             )
             : Text(
             _title,
             ),
     );
    },
   );
  })

I am trying to make each Container calls the http requests to get the title from my database. Get the title and then update to the Text() widget below. However, it doesn't get updated after the value has been returned from the server.

I could make them wait for the request to get the title by using FutureBuilder. I tried with FutureBuilder too. However, FutureBuilder was not also reactive to the variable changes. So, I am trying to do this here. I kinda get the problem. After, the widget is returned, it is not changeable? Is there any way that I can do it with GetX?

chichi
  • 2,777
  • 6
  • 28
  • 53

2 Answers2

29

Here's an example of using GetX with a Listview.builder.

This example uses a GetBuilder rather than Obx, as I'm not sure using a stream adds anything of benefit. If for some reason observables/streams are needed, numbers can be updated to be an .obs and the update() calls should be removed and GetBuilder replaced by GetX or Obx. If someone asks, I'll add that as an alternate example.

The GetBuilder wraps the ListView.builder and only the ListView will be rebuilt, not the entire widget tree / page.

import 'package:flutter/material.dart';
import 'package:get/get.dart';

class ListDataX extends GetxController {
  List<int> numbers = List<int>.from([0,1,2,3]);

  void httpCall() async {
    await Future.delayed(Duration(seconds: 1), 
            () => numbers.add(numbers.last + 1)
    );
    update();
  }

  void reset() {
    numbers = numbers.sublist(0, 3);
    update();
  }
}

class GetXListviewPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ListDataX dx = Get.put(ListDataX());
    print('Page ** rebuilt');
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              flex: 8,
              child: GetBuilder<ListDataX>(
                builder: (_dx) => ListView.builder(
                    itemCount: _dx.numbers.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        title: Text('Number: ${_dx.numbers[index]}'),
                      );
                    }),
              ),
            ),
            Expanded(
              flex: 1,
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    RaisedButton(
                      child: Text('Http Request'),
                      onPressed: dx.httpCall,
                    ),
                    RaisedButton(
                      child: Text('Reset'),
                      onPressed: dx.reset,
                    )
                  ],
                )
            )
          ],
        ),
      ),
    );
  }
}

Obx / Streams version

Here's the above solution using Rx streams & Obx widget.

class ListDataX2 extends GetxController {
  RxList<int> numbers = List<int>.from([0,1,2,3]).obs;

  void httpCall() async {
    await Future.delayed(Duration(seconds: 1),
            () => numbers.add(numbers.last + 1)
    );
    //update();
  }

  void reset() {
    numbers = numbers.sublist(0, 3);
    //update();
  }
}


class GetXListviewPage2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ListDataX2 dx = Get.put(ListDataX2());
    print('Page ** rebuilt');
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              flex: 8,
              child: Obx(
                () => ListView.builder(
                    itemCount: dx.numbers.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        title: Text('Number: ${dx.numbers[index]}'),
                      );
                    }),
              ),
            ),
            Expanded(
                flex: 1,
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    RaisedButton(
                      child: Text('Http Request'),
                      onPressed: dx.httpCall,
                    ),
                    RaisedButton(
                      child: Text('Reset'),
                      onPressed: dx.reset,
                    )
                  ],
                )
            )
          ],
        ),
      ),
    );
  }
}
Baker
  • 24,730
  • 11
  • 100
  • 106
  • I am practicing creating List with Getx. Wanted to interact with the server and apply Flutter animation on create or delete! Thank you so much. I will def check this out – chichi Jan 16 '21 at 17:47
  • Thanks for this great example. – Ugur May 21 '21 at 21:07
  • 1
    Hi thanks for the `GetBuilder` solution, can you add the `numbers` and `Obx` alternative solution please? I'd like to know the difference, I'm currently using that way but it doesn't seem to work. TIA! – Zennichimaro Nov 18 '21 at 14:10
  • 2
    @Zennichimaro Just added the `Obx` / `Rx` version above. – Baker Nov 19 '21 at 04:09
  • Hi @Baker thank you very much, it is indeed similar to what I've got, but I have some question which I posted here: https://stackoverflow.com/questions/70030287/flutter-itembuilder-not-called-when-controller-rx-data-changed-listitem-build can you help with it? – Zennichimaro Nov 19 '21 at 05:33
  • 1
    @Zennichimaro I took a look at your question. I'm not sure why it's not rebuilding for you. But I'm also not familiar with `GetWidget` and how it should be used. – Baker Nov 19 '21 at 15:53
  • We had a slightly different scenario. We had a list of immutable objects, e.g. Users, that was observable. We would update the User via a copyWith giving us a new object. This, of course, did not cause the list to be updated. We had to inject the new User into the list at the same index. That kept the view exactly as we wanted. Maybe not so obviously, making the User object variables observable was non-sensical for the object was immutable. – Bill Turner Feb 01 '22 at 15:57
0

I've not tested it due to the fact that I don't have a complete sample but I think this is what you are looking for:

FutureBuilder<String>(
  future: toDoController.getTodoInfo(_todo[i]), 
  builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
    if (snapshot.hasData) {
      return Container(
        height: 150,
        width: 150,
        child: Text(snapshot.data),
     );
    } else if (snapshot.hasError) {
      return Text('Error');
    } else {
      return CircularProgressIndicator();
    }
  },
),

This is the code you need to return for every item of list builder.

E.Benedos
  • 1,585
  • 14
  • 41
  • Oh, I've done it like this before. I got stuck when I was trying to remove one the `Todo Container()` with click without re-rendering. I wanted to have animation effect when it gets added or deleted to the List. Is there any way that I can do it with `FutureBuilder`? – chichi Jan 11 '21 at 21:48