0

The idea is to fetch data from api continuously and show this data in screen, without need to fetch manually, like that if someone else perform a change on server data this change will be shown without user refreshing action.

The implementation is done in Flutter BLoC (cubit).

I have already get a console print of data in the cubit but I can't get same data in the BlocBuilder neither in the BlocListener.

My code is:

//data_repository.dart

import 'dart:async';

import 'data.dart';

class DataRepository {
  final DataApi dataApi;
  List<Map<String, dynamic>> formatedData = [];
  final _controller = StreamController<List<Map<String, dynamic>>>();

  DataRepository(this.dataApi) {
    getData();
  }

  Future<void> getData() async {
    Timer.periodic(Duration(seconds: 5), (timer) async {
      formatedData.clear();
      Map<String, dynamic> res = await dataApi.getData();
      List<dynamic> data = res['data'];
      for (var el in data) {
        formatedData.add({'id': el['id'], 'name': el['name']});
      }
      _controller.add(formatedData);
    });
  }

  @override
  Stream<List<Map<String, dynamic>>> data() async* {
    yield* _controller.stream;
  }

  @override
  void dispose() => _controller.close();
}

Blockquote

This code is Data Repository it get data and make it available via a StreamController named "_controller";

Here data is got and controller is working perfectly.

My Cubit State is like this:

//data_list_state.dart

class DataListState extends Equatable {
  final List<Map<String, dynamic>> data;

  DataListState(this.data);

  @override
  List<Object> get props => [data];

  DataListState copyWith({
    List<Map<String, dynamic>>? data,
  }) {
    return DataListState(
      data ?? this.data,
    );
  }
}

When I print within copyWith() I get updated data;

My Cubit code:

//data_list_cubit.dart

class DataListCubit extends Cubit<DataListState> {
  DataListCubit(this.dataRepository) : super(DataListState([])) {
    loadList();
  }
  final DataRepository dataRepository;

  loadList() {
    dataRepository.data().listen((event) {
      if (event.isNotEmpty) {
        emit(state.copyWith(data: dataRepository.formatedData));
      }
    });
  }
}

When I print in loadList() I get the updated data;

My Screen Code:

//home.dart

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

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(children: [
        BlocListener<DataListCubit, DataListState>(
          listener: (context, state) {
            if (state.data.isNotEmpty) print(state.data[0].toString());
          },
          child: BlocBuilder<DataListCubit, DataListState>(
            builder: (context, state) {
              if (state.data.isNotEmpty) print(state.data[0].toString());
              return ListView(
                shrinkWrap: true,
                children: [
                  for (Map<String, dynamic> ff in state.data)
                    ListTile(
                      title: Text(ff['name']),
                      leading: Text(ff['id'].toString()),
                    ),
                ],
              );
            },
          ),
        ),
      ]),
    );
  }
}

When I console print here I don't get data just for the first time, and after every 5 secondes (described in my getData()) I get updated data in all my codes excepting in the home.

Can you tell me if my cubit implementation is right ?

What should I do to make it work ?

Thanks in advance

abdello
  • 158
  • 3
  • 13

1 Answers1

0

You must add the BlocProvider above the BlocListener or BlocBuilder in HomePage.

tip: Learn BlocListener vs BlocBuilder vs BlocConsumer. All three must be enclosed in BlocProvider to work


class MyHomePage extends StatelessWidget {

  DataRepository dataRepository = DataRepository();

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: BlocProvider<DataListCubit>(
        create: (context) => DataListCubit(dataRepository),
        child: Scaffold(
          body: BlocBuilder<DataListCubit, DataListState>(
            builder: (context, state) {
              print(state.data);
              if (state.data.isNotEmpty) {
                return ListView(
                  shrinkWrap: true,
                  children: [
                    for (Map<String, dynamic> ff in state.data)
                      ListTile(
                        title: Text(ff['name']),
                        leading: Text(ff['id'].toString()),
                      ),
                  ],
                );
              } else {
                return const Center(
                  child: Text('No Data'),
                );
              }
            },
          ),
        ),
      ),
    );
  }
}

Your Cubit State should be like below as you don't require Equitable for state management in Cubit implementation


class DataListState   {
  const DataListState(this.data);

  final List<Map<String, dynamic>> data;

  DataListState copyWith({
    List<Map<String, dynamic>>? data,
  }) {
    return DataListState(
      data ?? this.data,
    );
  }
}

Full code

In absence of DataApi, I have structured the whole working code with random data as in below :


import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:flutter_bloc/flutter_bloc.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Demo',
      theme: ThemeData(),
      home:  MyHomePage(),
      // home: VibrateHomepage(),
    );
  }
}

class MyHomePage extends StatelessWidget {

  DataRepository dataRepository = DataRepository();

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: BlocProvider<DataListCubit>(
        create: (context) => DataListCubit(dataRepository),
        child: Scaffold(
          body: BlocBuilder<DataListCubit, DataListState>(
            builder: (context, state) {
              print(state.data);
              if (state.data.isNotEmpty) {
                return ListView(
                  shrinkWrap: true,
                  children: [
                    for (Map<String, dynamic> ff in state.data)
                      ListTile(
                        title: Text(ff['name']),
                        leading: Text(ff['id'].toString()),
                      ),
                  ],
                );
              } else {
                return const Center(
                  child: Text('No Data'),
                );
              }
            },
          ),
        ),
      ),
    );
  }
}

class DataRepository {
  DataRepository() {
    getData();
  }

  DataApi dataApi = DataApi();
  List<Map<String, dynamic>> formattedData = [];
  final _controller = StreamController<List<Map<String, dynamic>>>();

  Future<void> getData() async {
    Timer.periodic(const Duration(seconds: 5), (timer) async {
      Map<String, dynamic> el =  dataApi.getNew();
      formattedData.add({'id': el['id'], 'name': el['name']});
      _controller.add(formattedData);
    });
  }

  Stream<List<Map<String, dynamic>>> data() async* {
    yield* _controller.stream;
  }

  void dispose() => _controller.close();
}

class DataApi {
  var rng = Random();
  getNew() {
    var rnd = rng.nextInt(100);
    return {
      "id": rnd,
      "name": "Person " + rnd.toString()
    };
  }
}

class DataListState   {
  const DataListState(this.data);

  final List<Map<String, dynamic>> data;

  DataListState copyWith({
    List<Map<String, dynamic>>? data,
  }) {
    return DataListState(
      data ?? this.data,
    );
  }
}

class DataListCubit extends Cubit<DataListState> {
  DataListCubit(this.dataRepository) : super(DataListState([])) {
    loadList();
  }
  final DataRepository dataRepository;

  loadList() {
    dataRepository.data().listen((event) {
      if (event.isNotEmpty) {
        emit(state.copyWith(data: dataRepository.formattedData));
      }
    });
  }
}


Amit Gayar
  • 41
  • 2
  • hey thank you for yr reply, I don't think it's BlocProvider related issue, because I already added it on my App() within MultiBlocProvider. And If I had a BlocProvider issue I got a provider related error instead, but here all things go fine just the show of update data. Thanks anyway. – abdello Jul 04 '22 at 15:34