1

I am building note application which stores notes in database (I am using floor).

My stream is listening data in database and every change that is occurred it will automatically rebuild the screen.

My problem is that when I implement the logic for toggleSelectAll (when I select notes they will change color) in usecase with for loop and update every note individually in database it is too slow. You can see how every note is selected when waiting for process to finish.

And now I have tried to write query for selecting all notes directly in database but my stream doesn't recognize it. When I call the usecase it will change the selection but rebuilld of the screen is not triggered. I can see the change when I restart the app.

Is there any solution for this or is there any other way to do selection which I didn't consider?

My code:

Note entity

class Note extends Equatable {
  @PrimaryKey(autoGenerate: true)
  final int? id;
  final String title;
  final String description;
  final String date;
  final bool isSelected;
  final bool isDeleted;
  final bool isFavorite;
  const Note({
    this.id,
    required this.title,
    required this.description,
    required this.date,
    this.isSelected = false,
    this.isDeleted = false,
    this.isFavorite = false,
  });

  Note copyWith(
      {ValueGetter<int?>? id,
      String? title,
      String? description,
      String? date,
      bool? isSelected,
      bool? isDeleted,
      bool? isFavorite}) {
    return Note(
        id: id != null ? id() : this.id,
        title: title ?? this.title,
        description: description ?? this.description,
        date: date ?? this.date,
        isSelected: isSelected ?? this.isSelected,
        isDeleted: isDeleted ?? this.isDeleted,
        isFavorite: isFavorite ?? this.isFavorite);
  }

  @override
  List<Object> get props =>
      [id!, title, description, date, isSelected, isDeleted, isFavorite];
}

NoteDao

@dao
abstract class NoteDao {
  @Query('SELECT * FROM NoteModel')
  Stream<List<NoteModel>> getNotes();

  @delete
  Future<void> removeNote(NoteModel noteModel);

  @insert
  Future<void> insertNote(NoteModel noteModel);

  @update
  Future<void> updateNote(NoteModel noteModel);

  @Query(
      'UPDATE NoteModel SET isSelected=CASE WHEN isSelected=1 THEN 0 ELSE 1 END')
  Future<void> toggleAllNotesSelect();
}

NoteLocalDataSourceImpl

class NoteLocalDataSourceImpl implements NoteLocalDataSource {
  final AppDatabase _appDatabase;

  const NoteLocalDataSourceImpl(this._appDatabase);

  @override
  Stream<List<NoteModel>> getNotes() {
    try {
      final notes = _appDatabase.noteDao.getNotes();
      return notes;
    } catch (error) {
      log(error.toString());
      throw const DatabaseException();
    }
  }

  @override
  Future<void> insertNote(NoteModel noteModel) async {
    try {
      return await _appDatabase.noteDao.insertNote(noteModel);
    } catch (error) {
      log(error.toString());
      throw const DatabaseException();
    }
  }

  @override
  Future<void> updateNote(NoteModel noteModel) async {
    try {
      return await _appDatabase.noteDao.updateNote(noteModel);
    } catch (error) {
      log(error.toString());
      throw const DatabaseException();
    }
  }

  @override
  Future<void> removeNote(NoteModel noteModel) async {
    try {
      return await _appDatabase.noteDao.removeNote(noteModel);
    } catch (error) {
      log(error.toString());
      throw const DatabaseException();
    }
  }

  @override
  Future<void> toggleAllSelectNotes() async {
    try {
      return await _appDatabase.noteDao.toggleAllNotesSelect();
    } catch (error) {
      log(error.toString());
      throw const DatabaseException();
    }
  }
}

NoteRepositoryImpl

class NoteRepositoryImpl implements NoteRepository {
  final NoteLocalDataSource _noteLocalDataSource;

  NoteRepositoryImpl(this._noteLocalDataSource);

  @override
  Future<Either<Failure, Stream<List<Note>>>> getNotes() async {
    try {
      final noteModelStream = _noteLocalDataSource.getNotes();
      final noteStream = noteModelStream.map((List<NoteModel> noteModels) =>
          noteModels.map((noteModel) => noteModel.toNote()).toList());
      return Right(noteStream);
    } on DatabaseException catch (error) {
      return Left(DatabaseFailure(error.message));
    }
  }

  @override
  Future<Either<Failure, Success>> insertNote(Note note) async {
    try {
      final noteModel = NoteModel.fromNote(note);
      await _noteLocalDataSource.insertNote(noteModel);
      return const Right(Success());
    } on DatabaseException catch (error) {
      return Left(DatabaseFailure(error.message));
    }
  }

  @override
  Future<Either<Failure, Success>> removeNote(Note note) async {
    try {
      final noteModel = NoteModel.fromNote(note);
      await _noteLocalDataSource.removeNote(noteModel);
      return const Right(Success());
    } on DatabaseException catch (error) {
      return Left(DatabaseFailure(error.message));
    }
  }

  @override
  Future<Either<Failure, Success>> updateNote(Note note) async {
    try {
      final noteModel = NoteModel.fromNote(note);
      await _noteLocalDataSource.updateNote(noteModel);
      return const Right(Success());
    } on DatabaseException catch (error) {
      return Left(DatabaseFailure(error.message));
    }
  }

  @override
  Future<Either<Failure, Success>> toggleAllSelectNotes() async {
    try {
      await _noteLocalDataSource.toggleAllSelectNotes();
      return const Right(Success());
    } on DatabaseException catch (error) {
      return Left(DatabaseFailure(error.message));
    }
  }
}

UseCase (commented part is where things get very slow so I tried this way)

class ToggleAllNotesSelectUseCase
    implements UseCase<Success, ToggleAllNotesSelectParams> {
  final NoteRepository _noteRepository;
  const ToggleAllNotesSelectUseCase(this._noteRepository);

  @override
  Future<Either<Failure, Success>> call(
      ToggleAllNotesSelectParams params) async {
    // final areAllCompleted = params.notes.every((note) => note.isSelected);
    // for (final note in params.notes) {
    //   final newNote = note.copyWith(isSelected: !areAllCompleted);
    //   await _noteRepository.updateNote(newNote);
    // }
    await _noteRepository.toggleAllSelectNotes();
    return const Right(Success());
  }
}

Part of the Bloc where I call UseCase

  void _onToggleAllNotesSelectEvent(
      ToggleAllNotesSelectEvent event, Emitter<NoteState> emit) async {
    await _noteUseCases
        .setAllNotesUnselectedUseCase(const ToggleAllNotesSelectParams());
  }

BlocBuilder where I return ListItemWidget which is ListView.builder for building notes

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      drawer: const MenuWidget(),
      floatingActionButton: const AddNoteButtonWidget(),
      appBar: AppBar(
        title: const Text(StringConstants.appbarHomeTitle),
        actions: [
          IconButton(
              onPressed: () => context.go(StringConstants.searchNotePageRoute),
              icon: const Icon(Icons.search)),
          const SortPopUpWidget(),
          const ToggleSelectPopUpWidget(),
        ],
      ),
      body: BlocBuilder<NoteBloc, NoteState>(
        builder: (context, state) {
          if (state.status == NoteStatus.success) {
            if (state.notes.isEmpty) {
              return const EmptyListWidget(
                  message: 'Empty', iconPath: StringConstants.imageNotesEmpty);
            } else {
              return ListItemWidget(state: state);
            }
          }

          if (state.status == NoteStatus.loading) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
          if (state.status == NoteStatus.failure) {
            return const Center(
              child: Text("Fail"),
            );
          } else {
            return const SizedBox();
          }
        },
      ),
    );
  }
}

ListItemWidget

class ListItemWidget extends StatelessWidget {
  final NoteState state;
  const ListItemWidget({
    super.key,
    required this.state,
  });

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: state.notes.length,
      itemBuilder: (context, index) {
        return Card(
          margin: const EdgeInsets.all(8),
          elevation: 5,
          color: state.notes[index].isSelected
              ? Colors.blueGrey[200]
              : Theme.of(context).cardColor,
          child: ListTile(
            onLongPress: () {
              context
                  .read<NoteBloc>()
                  .add(ToggleNoteSelectEvent(note: state.notes[index]));
            },
            onTap: () {
              context.go(StringConstants.updateNotePageRoute,
                  extra: state.notes[index]);
            },
            title: Text(
              state.notes[index].title,
              maxLines: 1,
              style: Theme.of(context).textTheme.labelLarge,
            ),
            subtitle: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  state.notes[index].description,
                  maxLines: 1,
                  style: Theme.of(context).textTheme.labelMedium,
                ),
                Text(
                  state.notes[index].date,
                  maxLines: 1,
                  style: Theme.of(context).textTheme.labelSmall,
                ),
              ],
            ),
            trailing: state.notes[index].isSelected
                ? IconButton(
                    onPressed: () {
                      context.read<NoteBloc>().add(const SetNoteDeletedEvent());
                    },
                    icon: Icon(
                      Icons.delete,
                      color: Theme.of(context).iconTheme.color,
                    ),
                  )
                : IconButton(
                    onPressed: () {},
                    icon: state.notes[index].isFavorite
                        ? Icon(
                            Icons.star,
                            color: Theme.of(context).colorScheme.primary,
                          )
                        : Icon(
                            Icons.star_border,
                            color: Theme.of(context).colorScheme.primary,
                          ),
                  ),
          ),
        );
      },
    );
  }
}
JtrProgg
  • 21
  • 2

0 Answers0