felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.56k stars 3.37k forks source link

State not update when adding a new item to the list #4114

Closed jonathanMNg closed 4 months ago

jonathanMNg commented 4 months ago

Description Maybe this is not a bug, but I can't figure out what I did wrong. I'm making a music player app that let the user download and listen to the music.

I'm having a problem at the "Add Track to Playlist" In this screen, the user will see a list of tracks that they already downloaded, and they can add them to their existing playlist, then close the screen. The problem is, after the tracks are added to the Playlist, the items list isn't updated when the "Add Track to Playlist" screen is closed, but if they exit out of the playlist, then go back, the added tracks then showed up.

Here is my code: Models:

import 'package:equatable/equatable.dart';
import 'package:isar/isar.dart';
part 'playlist_model.g.dart';

@Collection(inheritance: false)
final class PlaylistModel extends Equatable {
  const PlaylistModel({required this.name, this.imageUrl, this.imagePath, this.id, this.items });

  final Id? id;
  final String name;
  final String? imageUrl;
  final String? imagePath;
  final List<AudioTrack>? items;

  @override  @ignore
  List<Object?> get props => [id, name, imageUrl, imagePath, items];

  PlaylistModel copyWith({
    int? id,
    String? name,
    String? imageUrl,
    String? imagePath,
    List<AudioTrack>? items
  }) {
    return PlaylistModel(id: id??this.id, name: name??this.name, imageUrl: imageUrl??this.imageUrl, imagePath: imagePath??this.imagePath, items: items??this.items);
  }

}

@Embedded(inheritance: false)
class AudioTrack extends Equatable{
  late final int? id;
  late final String? name;
  late final String? artist;
  late final String? imageUrl;
  late final String? imagePath;
  late final String? audioPath;
  late final int? lastPosition;
  late final int? totalDuration;

  @override @ignore
  List<Object?> get props => [id, name, artist, imageUrl, imagePath];
}

State

part of 'playlist_bloc.dart';

abstract class PlaylistState extends Equatable {
  const PlaylistState();

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

class PlaylistInitial extends PlaylistState {
}

class PlaylistLoaded extends PlaylistState {
  const PlaylistLoaded(this.playlistList);
  final List<PlaylistModel> playlistList;

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

class PlaylistTrackLoaded extends PlaylistState {
  const PlaylistTrackLoaded(this.audioTrackList);

  final List<AudioTrack> audioTrackList;
  @override
  List<Object> get props => [audioTrackList];
}

class PlaylistTrackAdded extends PlaylistState {
  const PlaylistTrackAdded(this.audioTrackList);

  final List<AudioTrack> audioTrackList;
  @override
  List<Object> get props => [audioTrackList];
}

class PlaylistTrackLoading extends PlaylistState {
  const PlaylistTrackLoading(this.audioTrackList);

  final List<AudioTrack> audioTrackList;
  @override
  List<Object> get props => [audioTrackList];
}

Event

part of 'playlist_bloc.dart';

abstract class PlaylistEvent extends Equatable {
  const PlaylistEvent();
  @override
  List<Object> get props => [];
}

final class AddPlaylist extends PlaylistEvent {
  const AddPlaylist(this.playlist);

  final PlaylistModel playlist;
  @override
  List<Object> get props => [playlist];

}

final class DeletePlaylist extends PlaylistEvent {
  const DeletePlaylist();
}

final class LoadPlaylist extends PlaylistEvent {
  const LoadPlaylist();
}

class AddTrackToPlaylist extends PlaylistEvent {
  const AddTrackToPlaylist(this.playlistId, this.item);

  final int playlistId;
  final DownloadItem item;
}

class LoadPlaylistItem extends PlaylistEvent {
  const LoadPlaylistItem(this.playlistId);

  final int playlistId;
}

Bloc

import 'package:dalat/models/download/download_model.dart';
import 'package:dalat/models/playlist/playlist_model.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:isar/isar.dart';

part 'playlist_event.dart';
part 'playlist_state.dart';

class PlaylistBloc extends Bloc<PlaylistEvent, PlaylistState> {
  final isar = Isar.getInstance();

  PlaylistBloc() : super(PlaylistInitial()) {
    // on<PlaylistEvent>((event, emit) {
    //   // TODO: implement event handler
    // });

    on<LoadPlaylist>((event, emit) async {
      final playlistList = await isar!.playlistModels.where().findAll();
      emit(PlaylistLoaded(playlistList));
    });

    on<AddPlaylist>((event, emit) async {
      await isar!.writeTxn(() async {
        isar?.playlistModels.put(PlaylistModel(name: event.playlist.name, id: Isar.autoIncrement));
      });
      add(const LoadPlaylist());

    });

    on<AddTrackToPlaylist>((event, emit) async {
      final audioTrack = AudioTrack()
        ..audioPath = event.item.path
        ..name = event.item.name
        ..id = DateTime.now().millisecondsSinceEpoch
        ..artist = ''
        ..imagePath = ''
        ..imageUrl = ''
        ..totalDuration = 0
        ..lastPosition = 0;
      final playlist = await isar!.playlistModels.get(event.playlistId);
      PlaylistModel? newPlaylist = playlist?.copyWith(items: playlist.items?.toList()??<AudioTrack>[]..add(audioTrack));
      await isar!.writeTxn(() async => await isar!.playlistModels.put(newPlaylist!));

      add(LoadPlaylistItem(event.playlistId));
    });

    on<LoadPlaylistItem>((event, emit) async {
      emit(PlaylistInitial());
      final playlist = await isar!.playlistModels.get(event.playlistId);
      emit(PlaylistTrackLoaded(playlist?.items ?? <AudioTrack>[]));
    });
  }
}

Playlist Screen


import 'package:dalat/bloc/download/download_bloc.dart';
import 'package:dalat/bloc/playlist/playlist_bloc.dart';
import 'package:dalat/models/playlist/playlist_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class PlaylistItemScreen extends StatelessWidget {
  const PlaylistItemScreen({super.key, required this.playlist});

  final PlaylistModel playlist;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(playlist.name), actions: [
        IconButton(onPressed: () {
          Navigator.of(context).push(MaterialPageRoute(builder: (context) {
            context.read<DownloadBloc>().add(const LoadAudioFiles());
            return PlaylistAddingTrackScreen(playlist: playlist);
          }));
        }, icon: const Icon(Icons.add),)
      ],
      leading: IconButton(icon: const Icon(Icons.arrow_back,),onPressed: () {
        Navigator.of(context).pop();
        context.read<PlaylistBloc>().add(const LoadPlaylist());
      },),),
      body: BlocBuilder<PlaylistBloc, PlaylistState>(
        builder: (context, state) {
          if(state is PlaylistTrackAdded) {
            return ListView.builder(
              itemBuilder: (context, index) {
                return ListTile(title: Text(playlist.items?[index].name ?? ""),);
              },
              itemCount: playlist.items?.length??0,
            );
          }
          else if(state is PlaylistTrackLoaded) {
            return ListView.builder(
              itemBuilder: (context, index) {
                return ListTile(title: Text(playlist.items?[index].name ?? ""),);
              },
              itemCount: playlist.items?.length??0,
            );
          }
          else {
            return const Center(child: Text("Please add some tracks"),);
          }

        },
      ),
    );
  }
}

class PlaylistAddingTrackScreen extends StatelessWidget {
  const PlaylistAddingTrackScreen({super.key, required this.playlist});

  final PlaylistModel playlist;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Add to ${playlist.name}"),
      leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () {
        // context.read<PlaylistBloc>().add(LoadPlaylistItem(playlist.id!));
        Navigator.of(context).pop(true);
      },),
      ),
      body: BlocBuilder<DownloadBloc, DownloadState>(
        builder: (context, state) {
          if (state is DownloadLoaded) {
            return ListView.builder(itemBuilder: (context, index) {
              return ListTile(
                title: Text(state.items[index].name),
                onTap: () {
                  context.read<PlaylistBloc>().add(
                      AddTrackToPlaylist(playlist.id!, state.items[index]));
                },
              );
            }, itemCount: state.items.length,);
          }
          else {
            return const Center(child: Text("No Audio File"),);
          }
        },
      ),
    );
  }
}

Screenshots

2024-03-19_11-29-09 2024-03-19_11-29-25 2024-03-19_11-30-14 2024-03-19_11-30-49 2024-03-19_11-31-06

Please help.

jonathanMNg commented 4 months ago

Never mind. I'm an idiot. I figured out what I did wrong. I called

return ListTile(title: Text(playlist.items?[index].name ?? ""),);

instead of

return ListTile(title: Text(state.audioTrackList[index].name ?? ""),);

Closing this now.