abdello
abdello

Reputation: 158

Flutter: Stream data from rest api using Cubit

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

Upvotes: 0

Views: 5221

Answers (1)

Amit Gayar
Amit Gayar

Reputation: 39

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));
      }
    });
  }
}


Upvotes: -1

Related Questions