skip to Main Content

I’m having an issue with dynamically loading data into a PlutoGrid.

I’m not getting any error messages.

I am making sure that the JSON data I am getting from my API is populating a list of strings that is mapped to a list of PlutoRow objects. When I statically populate a list of strings and map that to the list of PlutoRow objects, that will display in the PlutoGrid. When I step thru my code, I noticed the following:

  1. The Widget build() function executes first and the rowsProviders list object is empty
  2. The initiState() function executes next and populates the rowsProviders list object (verified)
  3. The Widget build() function executes again and the rowsProviders list object now has data when I set the rows property of the PlutoGrid to it. HOWEVER, this has NO EFFECT on the grid. It has it’s column headers, but it has zero rows and there is no error message.

I have provided my code below. I have stripped out everything but this PlutoGrid. This is the code I am trying to debug.

import 'dart:convert';
import 'package:http/http.dart' as http;
import "package:flutter/material.dart";
import 'package:pluto_grid/pluto_grid.dart';

const String API_URL_LLMT_PROVIDER_ROUTE = "http://127.0.0.1:5000/get_mt_providers";


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

class MyApp extends StatelessWidget 
{
    const MyApp({Key? key}) : super(key: key);

    @override
    Widget build(BuildContext context) 
    {
        return MaterialApp
        (
            title: 'LL Translate',
            theme: ThemeData(primarySwatch: Colors.blue,),
            home: const DetectorPage(title: 'LLTS MT Tools'),
            debugShowCheckedModeBanner: true,
        );
    }
}

class DetectorPage extends StatefulWidget 
{
    const DetectorPage({Key? key, required this.title}) : super(key: key);
    final String title;

    @override
    State<DetectorPage> createState() => _DetectorPageState();
}

class _DetectorPageState extends State<DetectorPage>
{
    final List<PlutoColumn> columnsProviders = [];
    List<PlutoRow> rowsProviders = [];
    List<String> providers = [];
    
    @override
    void initState()
    {
        super.initState();

        columnsProviders.addAll 
        (
            [
                PlutoColumn
                (
                    width: 400,
                    backgroundColor: Colors.blue,
                    title: 'MT Provider',
                    field: 'MT Provider Tested',                        
                    type: PlutoColumnType.text(),
                    enableRowChecked: true,
                ),
                PlutoColumn
                (
                    width: 95,
                    backgroundColor: Colors.blue,
                    title: 'WER', 
                    field: 'Leven',
                    type: PlutoColumnType.text(),
                    enableFilterMenuItem: true,
                    textAlign: PlutoColumnTextAlign.center,
                    titleTextAlign: PlutoColumnTextAlign.center,
                ),
                PlutoColumn
                (
                    width: 100,
                    backgroundColor: Colors.blue,
                    title: 'BLEU',
                    field: 'BLEU',
                    type: PlutoColumnType.text(),
                    enableFilterMenuItem: false,
                    textAlign: PlutoColumnTextAlign.center,
                    titleTextAlign: PlutoColumnTextAlign.center,
                ),
                PlutoColumn
                (
                    width: 120,
                    backgroundColor: Colors.blue,
                    title: 'Combined',
                    field: 'Combined',
                    type: PlutoColumnType.text(),
                    enableFilterMenuItem: false,
                    textAlign: PlutoColumnTextAlign.center,
                    titleTextAlign: PlutoColumnTextAlign.center,
                ),
            ]
        );

        loadMtProviders();
    }

    void loadMtProviders() async 
    {
        // List<String> providersList = providers.cast<String>().toList();
        // List<String> providers = ['Google', 'Microsoft'];
        // List<String> providers = await getMtProviders();
        // providers = ['Google', 'Microsoft'];
        providers = await getMtProviders();

        rowsProviders = providers.map((item) 
        {
            return PlutoRow
            (
                cells: 
                {
                    'MT Provider Tested': 
                        PlutoCell
                        (
                            value: item,
                            
                        ), 
                    'Leven': PlutoCell(value: ''),
                    'BLEU': PlutoCell(value: ''),
                    'Combined': PlutoCell(value: ''),
                },
            );
        }).toList();


        setState(() 
        {

        });
    }
    
    Future<List<String>> getMtProviders() async 
    {
        var url = Uri.parse(API_URL_LLMT_PROVIDER_ROUTE);
        var response = await http.get(url);

        if (response.statusCode == 200)
        {
            var value = response.body;
            var jsonResponse = jsonDecode(value);

            List<String> result = jsonResponse.cast<String>();
            
            return result;
        } 
        else 
        {
            print('Request failed with status: ${response.statusCode}.');
            return ['0'];
        }
    }

    @override
    Widget build(BuildContext context) 
    {
        return Scaffold
        (
            body: SizedBox
            (
                child: PlutoGrid
                (
                    columns: columnsProviders,
                    rows: rowsProviders,
                ),
            ) 

        );
    }
}

Thanks in advance.

2

Answers


  1. Chosen as BEST ANSWER

    As @pixel pointed out, this is about state management in flutter. An easy way to do this is by using a StateManager object. PlutoGrid actually has a PlutoGridStateManager class for this purpose.

    You first define it in your PageState class:

    class _DetectorPageState extends State<DetectorPage>
    {
        late PlutoGridStateManager stateManagerProviders;
        ...
    }
    

    And then when you populate the grid, you add it to the PlutoGridStateManager:

    setState(() 
    {
        stateManagerProviders.appendRows(rowsProviders);
    });
    

    And finally, you wire up PlutoGrid events to the PlutoGridStateManager like so:

    @override
    Widget build(BuildContext context) 
    {
        ...
    
        child: PlutoGrid
        (
            columns: columnsProviders,
            rows: rowsProviders,
            onLoaded: (PlutoGridOnLoadedEvent event) 
            {
                stateManagerProviders = event.stateManager;
                stateManagerSummary.setSelectingMode(PlutoGridSelectingMode.row);
            },
        ),
    }
    

    This is a simple example. You can maintain the state of your PlutoGrid by wiring up additional events here.


  2. Just as i have assumed the issue was about the data was not getting updated.
    you need to use some state management for api calls and updating UI elements on this type of issues. here in the my resolve i have used cubit. you can read some articles for it.

    In short terms the cubit do some operations(which requires some finite amount of time) and emit a state which will be caught by widgets like BlocProvider, BlocListener, BlocBuilder.. and it will show some desired Widget on that

    So your getMtProviders() and loadMtProviders() functions will be moved to the cubit like below:

      class FetchDataCubit extends Cubit<FetchDataState> {
      FetchDataCubit() : super(FetchDataInitial());
    
      void fetchData() async {
        emit(FetchingData());
        List<String> providers = [];
    
        try {
          providers = await getMtProviders();
        } catch (e) {
          emit(FetchingDataError());
        }
    
        List<PlutoRow> rowsProviders = providers.map((item) {
          return PlutoRow(
            cells: {
              'MT Provider Tested': PlutoCell(
                value: item,
              ),
              'Leven': PlutoCell(value: ''),
              'BLEU': PlutoCell(value: ''),
              'Combined': PlutoCell(value: ''),
            },
          );
        }).toList();
    
        emit(FetchedData(rowsProviders));
      }
    
      Future<List<String>> getMtProviders() async {
        var url = Uri.parse(
            'https://cat-fact.herokuapp.com/facts/random?animal_type=cat&amount=10');
        var response = await http.get(url);
        if (response.statusCode == 200) {
          var value = response.body;
          List<dynamic> jsonResponse = jsonDecode(value);
    
          List<String> result =
              jsonResponse.map((e) => e['text'].toString()).toList();
    
          return result;
        } else {
          print('Request failed with status: ${response.statusCode}.');
          return ['0'];
        }
      }
    }
    

    This fetchData function will be called. and will emit FetchedData() state. Other cubit states are as below:

    @immutable
    abstract class FetchDataState {}
    
    class FetchDataInitial extends FetchDataState {}
    
    class FetchingData extends FetchDataState {}
    
    class FetchingDataError extends FetchDataState {}
    
    class FetchedData extends FetchDataState {
      final List<PlutoRow> data;
    
      FetchedData(this.data);
    }
    

    And your stateful class will be as:

    class DetectorPage extends StatefulWidget {
      const DetectorPage({Key? key, required this.title}) : super(key: key);
      final String title;
    
      @override
      State<DetectorPage> createState() => _DetectorPageState();
    }
    
    class _DetectorPageState extends State<DetectorPage> {
      final List<PlutoColumn> columnsProviders = [];
      final FetchDataCubit _fetchDataCubit = FetchDataCubit();
    
      @override
      void initState() {
        super.initState();
        _fetchDataCubit.fetchData();  // <- calling cubit function which will fetch the data.
        columnsProviders.addAll([
         // your code for adding columns 
        ]);
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            body: SafeArea(
          child: BlocBuilder(
            bloc: _fetchDataCubit,
            builder: (context, state) {
              if (state is FetchingData) {
                return const Center(
                  child: CircularProgressIndicator(),
                );
              } else if (state is FetchedData) {
                return PlutoGrid(
                  columns: columnsProviders,
                  rows: state.data,
                );
              } else if (state is FetchingDataError) {
                const Center(
                  child: Text('Some Error occurred in fetching data'),
                );
              }
              return const SizedBox.shrink();
            },
          ),
        ));
      }
    }
    

    just how i said here BlocBuilder is there, it will show the widget according to the states emitted by the cubit who’s instance we have take and passed to it as _fetchDataCubit.

    enter image description here

    Please the change api url..

    hope this would help you, happy learning

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search