UI Not updating on bloc state change

Issue

I am new in using bloc library with freezed package. I have a scenario where a list of objects is coming from API is displayed. Now the list tile has a Marked as Fav button and clicking upon it a event is trigged and fav bool is toggled and state is emitted.

The Issue :
The value of the object is changed but the UI is not updated accordingly.

Main.dart


import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_bloc_update/bloc/fakeapi_bloc.dart';

void main() {
  runApp(
    BlocProvider(
      create: (context) => FakeapiBloc(),
      child: const MyApp(),
    ),
  );
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const Scaffold(
        body: SafeArea(child: MyHomePage()),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    context.read<FakeapiBloc>().add(const FakeapiEvent.loadData());
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<FakeapiBloc, FakeapiState>(
      builder: (context, state) {
        return state.map(
          inital: (_) => const Center(
            child: CircularProgressIndicator(),
          ),
          loading: (_) => const Center(
            child: CircularProgressIndicator(),
          ),
          loaded: (state) {
            return ListView.builder(itemBuilder: (ctx, pos) {
              return ListTile(
                title: Text(state.posts[pos].title),
                trailing: IconButton(
                  onPressed: () {
                    context
                        .read<FakeapiBloc>()
                        .add(FakeapiEvent.markedFav(pos: pos));
                  },
                  icon: Icon(state.posts[pos].isFav
                      ? Icons.favorite
                      : Icons.favorite_border_outlined),
                ),
              );
            });
          },
          error: (_) => const Center(
            child: CircularProgressIndicator(),
          ),
        );
      },
    );
  }
}

Post.dart


import 'dart:convert';

List<Post> postFromJson(String str) =>
    List<Post>.from(json.decode(str).map((x) => Post.fromJson(x)));

String postToJson(List<Post> data) =>
    json.encode(List<dynamic>.from(data.map((x) => x.toJson())));

class Post {
  Post({
    required this.userId,
    required this.id,
    required this.title,
    required this.body,
  });

  final int userId;
  final int id;
  final String title;
  final String body;
  bool isFav = false;

  factory Post.fromJson(Map<String, dynamic> json) => Post(
        userId: json["userId"],
        id: json["id"],
        title: json["title"],
        body: json["body"],
      );

  Map<String, dynamic> toJson() => {
        "userId": userId,
        "id": id,
        "title": title,
        "body": body,
      };
}

bloc.dart


import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:freezed_bloc_update/post.dart';
import 'package:http/http.dart' as http;

part 'fakeapi_event.dart';
part 'fakeapi_state.dart';
part 'fakeapi_bloc.freezed.dart';

class FakeapiBloc extends Bloc<FakeapiEvent, FakeapiState> {
  FakeapiBloc() : super(const FakeapiState.inital()) {
    on<LoadData>(
      (event, emit) async {
        try {
          emit(const FakeapiState.loading());
          Uri uri = Uri.parse('https://jsonplaceholder.typicode.com/posts');
          final response = await http.get(uri);
          if (response.statusCode == 200) {
            emit(FakeapiState.loaded(posts: postFromJson(response.body)));
          } else {
            emit(const FakeapiState.error(errorMessage: 'Api Call Failed'));
          }
        } catch (err) {
          emit(const FakeapiState.error(errorMessage: 'Api Call Failed'));
        }
      },
    );

    on<MarkedFav>((event, emit) {
      final previousState = state as Loaded;
      previousState.posts[event.pos].isFav =
          !previousState.posts[event.pos].isFav;
      emit(FakeapiState.loaded(posts: [...previousState.posts]));
    });
  }
}

events.dart


part of 'fakeapi_bloc.dart';

@freezed
class FakeapiEvent with _$FakeapiEvent {
  const factory FakeapiEvent.loadData() = LoadData;
  const factory FakeapiEvent.markedFav({required int pos}) = MarkedFav;
}

states.dart


part of 'fakeapi_bloc.dart';

@freezed
class FakeapiState with _$FakeapiState {
  const factory FakeapiState.inital() = Initial;
  const factory FakeapiState.loading() = Loading;
  const factory FakeapiState.loaded({required List<Post> posts}) = Loaded;
  const factory FakeapiState.error({required String errorMessage}) = Error;
}

One solution which I did was keeping a bool variable(outside of model class) in state itself and clicking upon the fav toggling the bool value. Which is retriggering the UI update.

Solution

your Post class should be converted to a freezed class

Answered By – Mahmoud Abdellatief

Answer Checked By – Robin (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.