Reputation: 158
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
Reputation: 39
You must add the BlocProvider
above the BlocListener
or BlocBuilder
in HomePage
.
tip: Learn
BlocListener
vsBlocBuilder
vsBlocConsumer
. All three must be enclosed inBlocProvider
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,
);
}
}
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