I'm learning Flutter and there is something I cannot grasp my head around.
I implemented a Infinite scroll pagination, with a package (infine_scroll_pagination),
it works fine, but the data this Package is getting, comes from a Future call, which takes data from the WEB, and parses it in my Provider Class.
My issue is, the data that is loaded by the Infinite Scroll widget, cannot be accessed, in its state, anywhere else.
Example:
Let's take a contact list, that loads 10 contacts at a time:
class ContactsBody extends StatefulWidget {
#override
_ContactsBodyState createState() => _ContactsBodyState();
}
class _ContactsBodyState extends State<ContactsBody> {
static const _pageSize = 10;
final PagingController<int, Contact> pagingController =
PagingController(firstPageKey: 0);
#override
void initState() {
super.initState();
pagingController.addPageRequestListener((pageKey) {
_fetchPage(pageKey);
});
}
Future<void> _fetchPage(int pageKey) async {
try {
final newItems = await ContactsService().fetchContactsPaged(pageKey, _pageSize);
final isLastPage = newItems.length < _pageSize;
if (isLastPage) {
pagingController.appendLastPage(newItems.contacts);
} else {
final nextPageKey = pageKey + 1;
pagingController.appendPage(newItems.contacts, nextPageKey);
}
} catch (error) {
pagingController.error = error;
}
}
#override
Widget build(BuildContext context) {
return ContactsList(pagingController);
}
#override
void dispose() {
pagingController.dispose();
super.dispose();
}
So basically this Infinite Scroll package, will fetch my contacts, 10 at a time, and here my ContactsService call:
Future<Contacts> fetchContactsPaged(int pageKey, int pageSize) async {
final response = await http.get(.....);
if (response.statusCode == 200) {
return Contacts.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load contacts');
}
}
And finally, as you can see here above, it initializes my Provider class (Contacts), using its factory method, "fromJson()", and returns the parsed data.
Now my Provider class:
class Contacts extends ChangeNotifier {
List<Contact> _contacts = <Contact>[];
Contacts();
factory Contacts.fromJson(final Map<String, dynamic> json) {
final Contacts contacts = Contacts();
if (json['data'] != null) {
json['data'].forEach((contact) {
contacts.add(Contact.fromJson(contact));
});
}
return contacts;
}
void add(final Contact contact) {
this._contacts.add(contact);
this.notifyListeners();
}
The problem I'm having here is, when the Inifinite Scroll listView is loaded, and for example I change the state of a single contact (contacts can be set as favorite for example),
How can I access the SAME instance of the Contacts() class, that the FUTURE call initialized, so that I can access the current state of the data in that class?
Of course if I were to POST my changes onto the API, and refetch the new values where I need them, I would get the updated state of my data, but I want to understand how to access the same instance here and make the current data available inside the app everywhere
EDIT : I removed the original answer to give a better sample of what the OP wants to achieve.
I made a repo on GitHub to try to show you what you want to achieve: https://github.com/Kobatsu/stackoverflow_66578191
There are a few confusing things in your code :
When to create instances of your objects (ContactsService, Contacts)
Provider usage
(Accessing the list of the pagingController ?)
Parsing a JSON / using a factory method
The repository results in the following :
When you update the list (by scrolling down), the yellow container is updated with the number of contacts and the number of favorites.
If you click on a Contact, it becomes a favorite and the yellow container is also updated.
I commented the repository to explain you each part.
Note: the Contacts class in your code became ContactProvider in mine.
The ContactsService class to make the API call :
class ContactsService {
static Future<List<Contact>> fetchContactsPaged(
int pageKey, int pageSize) async {
// Here, you should get your data from your API
// final response = await http.get(.....);
// if (response.statusCode == 200) {
// return Contacts.fromJson(jsonDecode(response.body));
// } else {
// throw Exception('Failed to load contacts');
// }
// I didn't do the backend part, so here is an example
// with what I understand you get from your API:
var responseBody =
"{\"data\":[{\"name\":\"John\", \"isFavorite\":false},{\"name\":\"Rose\", \"isFavorite\":false}]}";
Map<String, dynamic> decoded = json.decode(responseBody);
List<dynamic> contactsDynamic = decoded["data"];
List<Contact> listOfContacts =
contactsDynamic.map((c) => Contact.fromJson(c)).toList();
// you can return listOfContacts, for this example, I will add
// more Contacts for the Pagination plugin since my json only has 2 contacts
for (int i = pageKey + listOfContacts.length; i < pageKey + pageSize; i++) {
listOfContacts.add(Contact(name: "Name $i"));
}
return listOfContacts;
}
}
Usage of Provider :
Consumer<ContactProvider>(
builder: (_, foo, __) => Container(
child: Text(
"${foo.contacts.length} contacts - ${foo.contacts.where((c) => c.isFavorite).length} favorites"),
padding: EdgeInsets.symmetric(
horizontal: 20, vertical: 10),
color: Colors.amber,
)),
Expanded(child: ContactsBody())
]),
)
Fetch page method in the ContactsBody class, where we add the contact to our ContactProvider :
Future<void> _fetchPage(int pageKey) async {
try {
// Note : no need to make a ContactsService, this can be a static method if you only need what's done in the fetchContactsPaged method
final newItems =
await ContactsService.fetchContactsPaged(pageKey, _pageSize);
final isLastPage = newItems.length < _pageSize;
if (isLastPage) {
_pagingController.appendLastPage(newItems);
} else {
final nextPageKey = pageKey + newItems.length;
_pagingController.appendPage(newItems, nextPageKey);
}
// Important : we add the contacts to our provider so we can get
// them in other parts of our app
context.read<ContactProvider>().addContacts(newItems);
} catch (error) {
print(error);
_pagingController.error = error;
}
}
ContactItem widget, in which we update the favorite statuts and notify the listeners :
class ContactItem extends StatefulWidget {
final Contact contact;
ContactItem({this.contact});
#override
_ContactItemState createState() => _ContactItemState();
}
class _ContactItemState extends State<ContactItem> {
#override
Widget build(BuildContext context) {
return InkWell(
child: Padding(child: Row(children: [
Expanded(child: Text(widget.contact.name)),
if (widget.contact.isFavorite) Icon(Icons.favorite)
]), padding: EdgeInsets.symmetric(vertical: 8, horizontal: 10),),
onTap: () {
// the below code updates the item
// BUT others parts of our app won't get updated because
// we are not notifying the listeners of our ContactProvider !
setState(() {
widget.contact.isFavorite = !widget.contact.isFavorite;
});
// To update other parts, we need to use the provider
context.read<ContactProvider>().notifyContactUpdated(widget.contact);
});
}
}
And the ContactProvider :
class ContactProvider extends ChangeNotifier {
final List<Contact> _contacts = [];
List<Contact> get contacts => _contacts;
void addContacts(List<Contact> newContacts) {
_contacts.addAll(newContacts);
notifyListeners();
}
void notifyContactUpdated(Contact contact) {
// You might want to update the contact in your database,
// send it to your backend, etc...
// Here we don't have these so we just notify our listeners :
notifyListeners();
}
}
Related
I am trying to implement pagination in my Flutter app but this is the first time I have done it. My idea was to create a Stream of data that updates each time the user reaches the bottom of a list. I have failed to get this working. My current code has the logic for getting the new data and adding it to the existing data, but right now it's not even returning the first range of data so the snapshots are empty. As for the pagination functionality, I tried to use a ChangeNotifier to notify the Stream to update, but I don't know if that is working. Please take a look at the code below and let me know what should be changed.
The DataProvider class:
class DataProvider extends ChangeNotifier{
DataProvider() : super();
static var changeController = ChangeNotifier();
static void reload() {
changeController.notifyListeners();
}
static int lastRange = 0;
static List data = [];
static Stream<List> paginatedUsersAndPosts() async* {
List<UserSearchResult> usersList = data.first;
List<Post> postsList = data.last;
print('Notifier');
changeController.addListener(() async {
print('Change notified, getting data');
List<int> range() {
if (lastRange == 0) {
lastRange = 10;
return [0, 10];
} else {
// Example 0, 10 => 11, 20
int newMin = lastRange + 1;
int newMax = lastRange + 10;
lastRange = newMax;
return [newMin, newMax];
}
}
List<Map<String, dynamic>> postsDocs = await Supabase.db
.from('posts')
.select()
.order('date', ascending: false)
.range(range().first, range().last);
List<Post> newPostsList =
postsDocs.map((postDoc) => Post.fromJson(postDoc)).toList();
newPostsList.forEach((post) async {
postsList.add(post);
if (usersList.where((u) => u.uid == post.uid).isNotEmpty) {
Map<String, dynamic> userDoc =
await Supabase.db.from('profiles').select().single();
ProfileInfoObject profileInfo = ProfileInfoObject.fromJson(userDoc);
print('New profile: $profileInfo');
Profile profile = Profile(profileInfo, []);
profile.posts.add(post);
List blockedUsers = userDoc['blockedUsers'] as List;
UserSearchResult user = (UserSearchResult(
profile, userDoc['uid'].toString(), blockedUsers));
usersList.add(user);
}
});
});
yield [usersList, postsList];
}
}
The main widget that uses the stream:
class FloatingTabBarView extends StatefulWidget {
const FloatingTabBarView({Key? key}) : super(key: key);
#override
State<FloatingTabBarView> createState() => _FloatingTabBarViewState();
}
class _FloatingTabBarViewState extends State<FloatingTabBarView> {
#override
void initState() {
PermissionsService.checkPermissions(context);
DataProvider.reload();
super.initState();
}
Stream<List> stream = DataProvider.paginatedUsersAndPosts();
return StreamBuilder<List>(
stream: stream,
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
...
});
}
#override
Widget build(BuildContext context) {
return floatingTabBarPageView();
}
}
Please take a look at pull_to_refresh package. This allows to implement pull to refresh and incrementally load data. Use the RefreshController to update when the data is refreshed or loaded.
Instantiate RefreshController
Wrap the ListView with SmartRefresher
Implement the onLoading callback to fetch data incrementally
Update the RefreshController on completion or error.
The package has a good usage example, do check it out.
The issue was the way I was creating the stream. Since I made the Stream a function, there was no way for me to call that function to reload. Instead, I moved the code to a static void that I can call from any page, created a StreamController, and used controller.add(data) at the end of the void which allows me to update the stream with the new data.
I'm fairly new to Flutter providers. I use Riverpod.
I have a Future provider that provide some data from a JSON file - in the future it will be from a API response.
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/pokemon.dart';
final pokemonProvider = FutureProvider<List<Pokemon>>((ref) async {
var response =
await rootBundle.loadString('assets/mock_data/pokemons.json');
List<dynamic> data = jsonDecode(response);
return List<Pokemon>.from(data.map((i) => Pokemon.fromMap(i)));
});
I subscribe to with ref.watch in ConsumerState widgets, e.g.:
class PokemonsPage extends ConsumerStatefulWidget {
const PokemonsPage({Key? key}) : super(key: key);
#override
ConsumerState<PokemonsPage> createState() => _PokemonsPageState();
}
class _PokemonsPageState extends ConsumerState<PokemonsPage> {
#override
Widget build(BuildContext context) {
final AsyncValue<List<Pokemon>> pokemons =
ref.watch(pokemonProvider);
return pokemons.when(
loading: () => const CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
data: (pokemons) {
return Material(
child: ListView.builder(
itemCount: pokemons.length,
itemBuilder: (context, index) {
Pokemon pokemon = pokemons[index];
return ListTile(
title: Text(pokemon.name),
);
},
));
},
);
}
}
But in that case, what is the best practice to write/update data to the JSON file/API?
It seems providers are used for reading/providing data, not updating it, so I'm confused.
Should the same provider pokemonProvider be used for that? If yes, what is the FutureProvider method that should be used and how to call it? If not, what is the best practice?
I am new to riverpod too but I'll try to explain the approach we took.
The examples with FutureProviders calling to apis are a little bit misleading for me, because the provider only offers the content for a single api call, not access to the entire api.
To solve that, we found the Repository Pattern to be very useful. We use the provider to export a class containing the complete api (or a mock one for test purposes), and we control the state (a different object containing the different situations) to manage the responses and updates.
Your example would be something like this:
First we define our state object:
enum PokemonListStatus { none, error, loaded }
class PokemonListState {
final String? error;
final List<Pokemon> pokemons;
final PokemonListStatus status;
const PokemonListState.loaded(this.pokemons)
: error = null,
status = PokemonListStatus.loaded,
super();
const PokemonListState.error(this.error)
: pokemons = const [],
status = PokemonListStatus.error,
super();
const PokemonListState.initial()
: pokemons = const [],
error = null,
status = PokemonListStatus.none,
super();
}
Now our provider and repository class (abstract is optional, but let's take that approach so you can keep the example for testing):
final pokemonRepositoryProvider =
StateNotifierProvider<PokemonRepository, PokemonListState>((ref) {
final pokemonRepository = JsonPokemonRepository(); // Or ApiRepository
pokemonRepository.getAllPokemon();
return pokemonRepository;
});
///
/// Define abstract class. Useful for testing
///
abstract class PokemonRepository extends StateNotifier<PokemonListState> {
PokemonRepository()
: super(const PokemonListState.initial());
Future<void> getAllPokemon();
Future<void> addPokemon(Pokemon pk);
}
And the implementation for each repository:
///
/// Class to manage pokemon api
///
class ApiPokemonRepository extends PokemonRepository {
ApiPokemonRepository() : super();
Future<void> getAllPokemon() async {
try {
// ... calls to API for retrieving pokemon
// updates cached list with recently obtained data and call watchers.
state = PokemonListState.loaded( ... );
} catch (e) {
state = PokemonListState.error(e.toString());
}
}
Future<void> addPokemon(Pokemon pk) async {
try {
// ... calls to API for adding pokemon
// updates cached list and calls providers watching.
state = PokemonListState.loaded([...state.pokemons, pk]);
} catch (e) {
state = PokemonListState.error(e.toString());
}
}
}
and
///
/// Class to manage pokemon local json
///
class JsonPokemonRepository extends PokemonRepository {
JsonPokemonRepository() : super();
Future<void> getAllPokemon() async {
var response =
await rootBundle.loadString('assets/mock_data/pokemons.json');
List<dynamic> data = jsonDecode(response);
// updates cached list with recently obtained data and call watchers.
final pokemons = List<Pokemon>.from(data.map((i) => Pokemon.fromMap(i)));
state = PokemonListState.loaded(pokemons);
}
Future<void> addPokemon(Pokemon pk) async {
// ... and write json to disk for example
// updates cached list and calls providers watching.
state = PokemonListState.loaded([...state.pokemons, pk]);
}
}
Then in build, your widget with a few changes:
class PokemonsPage extends ConsumerStatefulWidget {
const PokemonsPage({Key? key}) : super(key: key);
#override
ConsumerState<PokemonsPage> createState() => _PokemonsPageState();
}
class _PokemonsPageState extends ConsumerState<PokemonsPage> {
#override
Widget build(BuildContext context) {
final statePokemons =
ref.watch(pokemonRepositoryProvider);
if (statePokemons.status == PokemonListStatus.error) {
return Text('Error: ${statePokemons.error}');
} else if (statePokemons.status == PokemonListStatus.none) {
return const CircularProgressIndicator();
} else {
final pokemons = statePokemons.pokemons;
return Material(
child: ListView.builder(
itemCount: pokemons.length,
itemBuilder: (context, index) {
Pokemon pokemon = pokemons[index];
return ListTile(
title: Text(pokemon.name),
);
},
));
}
}
}
Not sure if this is the best approach but it is working for us so far.
you can try it like this:
class Pokemon {
Pokemon(this.name);
final String name;
}
final pokemonProvider =
StateNotifierProvider<PokemonRepository, AsyncValue<List<Pokemon>>>(
(ref) => PokemonRepository(ref.read));
class PokemonRepository extends StateNotifier<AsyncValue<List<Pokemon>>> {
PokemonRepository(this._reader) : super(const AsyncValue.loading()) {
_init();
}
final Reader _reader;
Future<void> _init() async {
final List<Pokemon> pokemons;
try {
pokemons = await getApiPokemons();
} catch (e, s) {
state = AsyncValue.error(e, stackTrace: s);
return;
}
state = AsyncValue.data(pokemons);
}
Future<void> getAllPokemon() async {
state = const AsyncValue.loading();
/// do something...
state = AsyncValue.data(pokemons);
}
Future<void> addPokemon(Pokemon pk) async {}
Future<void> updatePokemon(Pokemon pk) async {}
Future<void> deletePokemon(Pokemon pk) async {}
}
In the application, when I click on the button, I write the name of the group in the field and add it to the database. The name of the group is also displayed on the screen. My problem is that every time I create a new group, it is overwritten on the screen.
And I need to display the names of the groups on the screen as a list and so that it is added and not overwritten. But the implementation is such that in my state there is not a list and it is impossible to display the names of groups using map. How can I display the titles as a list so they don't get overwritten?
#immutable
abstract class NewGroupState {
const NewGroupState();
#override
List<Object?> get props => [];
}
class NewGroupInitial extends NewGroupState {
const NewGroupInitial();
}
class AddGroupState extends NewGroupState {
const AddGroupState(this.group);
final Group group;
}
body: BlocBuilder<NewGroupBloc, NewGroupState>(
builder: (context, state) {
return ListTile(
title: Text(state is AddGroupState ? state.group.groupName : ''),
);
},
),
I'm not sure I understood your question, is this what you're trying to do?
If not, can you rewrite your question?
class AddGroupState extends NewGroupState {
const AddGroupState(this.currentGroup, this.previousGroups);
final Group currentCroup;
final List<Group> previousGroups;
}
class NewGroupBloc extends Bloc<NewGroupState> {
Future<void> addGroupToDB() async {
final newGroup = Group();
try {
// add group to DB
await _groupRepository.add(newGroup);
final updatedGroups = [...state.previousGroups, newGroup];
emit(NewGroupState(newGroup, updatedGroups));
} on DBError catch (e) {
emit(NewGroupErrorState());
}
}
}
Edit: this is a BAD answer but author asked how it could be done.
class NewGroupBloc extends Bloc<NewGroupState> {
late Future<void> Function(Group group) onAddGroup;
Future<void> addGroupToDB() async {
final newGroup = Group();
try {
await _groupRepository.add(newGroup);
await onAddGroup(newGroup);
emit(NewGroupState(newGroup));
} on DBError catch (e) {
emit(NewGroupErrorState());
}
}
}
class _GroupPageState extends State<GroupPage> {
var groups = <Group>[];
NewGroupBloc bloc = // don't know how it was initialized
#override
void initState() {
bloc.onAddGroup = (newGroup) {
setState(() {
groups = [...groups, newGroup];
});
}
}
Widget build(BuildContext context) {
// The same as you already have, but using groups declared in the Widget instead of BLoC's state.
}
}
This is what I'm trying to achieve using flutter GetX package but not working properly.
I have a Firestore document, if the document is changed I want to call an api and keep the data up to date as observable.
The code below seems to work but initial screen shows null error then it shows the data.
I don't know how I can make sure both fetchFirestoreUser() and fetchApiData() (async methods) returns data before I move to the home screen.
GetX StateMixin seems to help with async data load problem but then I don't know how I can refresh the api data when the firestore document is changed.
I'm not sure if any other state management would be best for my scenario but I find GetX easy compared to other state management package.
I would very much appreciate if someone would tell me how I can solve this problem, many thanks in advance.
Auth Controller.
class AuthController extends SuperController {
static AuthController instance = Get.find();
late Rx<User?> _user;
FirebaseAuth auth = FirebaseAuth.instance;
var _firestoreUser = FirestoreUser().obs;
var _apiData = ProfileUser().obs;
#override
void onReady() async {
super.onReady();
_user = Rx<User?>(auth.currentUser);
_user.bindStream(auth.userChanges());
//get firestore document
fetchFirestoreUser();
//fetch data from api
fetchApiData();
ever(_user, _initialScreen);
//Refresh api data if firestore document has changed.
_firestoreUser.listen((val) {
fetchApiData();
});
}
Rx<FirestoreUser?> get firestoreUser => _firestoreUser;
_initialScreen(User? user) {
if (user == null) {
Get.offAll(() => Login());
} else {
Get.offAll(() => Home());
}
}
ProfileUser get apiData => _apiData.value;
void fetchFirestoreUser() async {
Stream<FirestoreUser> firestoreUser =
FirestoreDB().getFirestoreUser(_user.value!.uid);
_firestoreUser.bindStream(firestoreUser);
}
fetchApiData() async {
var result = await RemoteService.getProfile(_user.value!.uid);
if (result != null) {
_apiData.value = result;
}
}
#override
void onDetached() {}
#override
void onInactive() {}
#override
void onPaused() {}
#override
void onResumed() {
fetchApiData();
}
}
Home screen
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Obx(() =>
Text("username: " + AuthController.instance.apiData.username!))),
),
);
}
}
To be honest, I never used GetX so I'm not too familiar with that syntax.
But I can see from your code that you're setting some mutable state when you call this method:
fetchApiData() async {
var result = await RemoteService.getProfile(_user.value!.uid);
if (result != null) {
_apiData.value = result;
}
}
Instead, a more robust solution would be to make everything reactive and immutable. You could do this by combining providers if you use Riverpod:
final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) {
final authService = ref.watch(authRepositoryProvider);
return authService.authStateChanges();
});
final apiDataProvider = FutureProvider.autoDispose<APIData?>((ref) {
final userValue = ref.watch(authStateChangesProvider);
final user = userValue.value;
if (user != null) {
// note: this should also be turned into a provider, rather than using a static method
return RemoteService.getProfile(user.uid);
} else {
// decide if it makes sense to return null or throw and exception when the user is not signed in
return Future.value(null);
}
});
Then, you can just use a ConsumerWidget to watch the data:
#override
Widget build(BuildContext context, WidgetRef ref) {
// this will cause the widget to rebuild whenever the auth state changes
final apiData = ref.watch(apiDataProvider);
return apiData.when(
data: (data) => /* some widget */,
loading: () => /* some loading widget */,
error: (e, st) => /* some error widget */,
);
}
Note: Riverpod has a bit of a learning curve (worth it imho) so you'll have to learn it how to use it first, before you can understand how this code works.
Actually the reason behind this that you put your controller in the same page that you are calling so in the starting stage of your page Get.put() calls your controller and because you are fetching data from the API it takes a few seconds/milliseconds to get the data and for that time your Obx() renders the error. To prevent this you can apply some conditional logic to your code like below :
Obx(() => AuthController.instance.apiData != null ? Text("username: " + AuthController.instance.apiData.username!) : CircularProgressIndicator())) :
So I have a simple list that's clickable and goes to DetailScreen, issue I have is when I click back from the DetailScreen, how can I manage this state to save the last list?
Bloc
if (event is GetNews && !_hasReachedMax(state)) {
try {
if (currentState is NewsInitial) {
final news = await fetchNews(event.cat, pageNumber);
yield NewsLoaded(news, false);
}
if (currentState is NewsLoaded) {
pageNumber++;
final news = await fetchNews(event.cat, pageNumber);
yield news.isEmpty
? currentState.copyWith(hasReachedMax: true)
: NewsLoaded(currentState.node + news, false);
}
} catch (error) {
print(error);
yield NewsError("Error fetching news" + error);
}
} else if (event is GetDetailedNews) {
try {
final filter = await fetchDetailedNews(event.id);
yield DetailedNewsLoaded(filter);
} catch (error) {
yield NewsError("Couldn't fetch news : $error");
}
}
Attaching the event to the bloc
#override
void initState() {
super.initState();
_postBloc = BlocProvider.of<NewsBloc>(context)
..add(GetNews(widget.cat));
}
BlocBuilder
OnBackPressed I'm just stick in the else since I don't know how to manage the state
return BlocBuilder<NewsBloc, NewsState>(builder: (context, state) {
if (state is NewsLoaded) {
return ListView.builder(
controller: _scrollController,
itemCount: state.hasReachedMax
? state.node.length
: state.node.length + 1,
itemBuilder: (context, index) {
fullList = state.node;
print("list: ${state.node} \nlength: ${state.node
.length} \nindex: $index \n--------------");
return index >= state.node.length ?
BottomLoader() :
listViews(context, state.node[index], index);
});
}
else if (state is NewsError) {
return Center(
child: Container(
child: Text(state.message),
));
}
else {
return Center(child: CircularProgressIndicator(),);
}
});
States
abstract class NewsState extends Equatable {
const NewsState();
#override
List<Object> get props => [];
}
class NewsInitial extends NewsState {
const NewsInitial();
#override
List<Object> get props => [];
}
class NewsLoading extends NewsState {
const NewsLoading();
#override
List<Object> get props => [];
}
class NewsLoaded extends NewsState {
final List<Node> node;
final bool hasReachedMax;
NewsLoaded(this.node, this.hasReachedMax);
NewsLoaded copyWith({List<Node> node, bool hasReachedMax}) {
return NewsLoaded(node ?? this.node, hasReachedMax ?? this.hasReachedMax);
}
#override
List<Object> get props => [node];
}
class DetailedNewsLoaded extends NewsState {
final List<Node> node;
DetailedNewsLoaded(this.node);
#override
List<Object> get props => [node];
}
}
In the detail screen i add the GetDetailScreen event, and this event stays when onBackPressed
#override
void initState() {
BlocProvider.of<NewsBloc>(context)
..add(GetDetailedNews(widget.id));
super.initState();
}
I believe the problem is that your state when you press to see the article changes to DetailedNewsLoaded. So when you press back BlocBuilder<NewsBloc, NewsState> goes to the else state which returns the CircularProgressIndicator.
As i understand in your case you don't need the DetailedNewsLoaded state. You can just need to pass the state.node to DetailsScreen as a simple argument.
Why to do so much when you already have the hero widget in Flutter.
Make a List.
Use Hero animation for both list items and their details view.
Whenever the list item is clicked the details view will be shown.
When user presses back button, he/she will come to the position where that particular list item was.
So, basically you don't have to much.
I was going through some projects and I found this on github: https://github.com/whatsupcoders/Flutter-Hero-Widget
This project is walked through in this video that I found on YouTube: https://www.youtube.com/watch?v=mgSB5r11_Xw&t=15s
this project uses Hero widget, I hope it helps you.