Skip to content

Commit

Permalink
chore: cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertBrunhage committed Oct 29, 2024
1 parent a17540f commit 31d3dea
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 98 deletions.
34 changes: 26 additions & 8 deletions lib/fake_data_source.dart → lib/database.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
import 'dart:async';

class FakeDataSource {
Future<void> add() async {
// In a real app, this would be a call to a local database or a remote API
import 'package:todo/features/todo/todo_entity.dart';
import 'package:uuid/uuid.dart';

class InMemoryDataSource {
final BehaviorSubject<List<TodoEntity>> _todosController =
BehaviorSubject([]);

Stream<List<TodoEntity>> get stream => _todosController.stream;

void add({required String title}) {
const uuid = Uuid();
final id = uuid.v4();
final todo = TodoEntity(id: id, title: title, completed: false);
_todosController.add([..._todosController.value, todo]);
}

Future<void> remove() async {
// In a real app, this would be a call to a local database or a remote API
void remove(TodoEntity todo) {
_todosController
.add(_todosController.value.where((t) => t.id != todo.id).toList());
}

Future<void> update() async {
// In a real app, this would be a call to a local database or a remote API
void update(TodoEntity updatedTodo) {
final updatedTodoEntitys = _todosController.value.map((t) {
if (t.id == updatedTodo.id) {
return updatedTodo;
}
return t;
}).toList();

_todosController.add(updatedTodoEntitys);
}
}

// A simple implementation of a reactive stream. Provides both a stream and a value.
class BehaviorSubject<T> {
// StreamController with broadcast mode
final StreamController<T> _controller;
Expand Down
32 changes: 0 additions & 32 deletions lib/features/todo/todo.dart

This file was deleted.

16 changes: 9 additions & 7 deletions lib/features/todo/todo_entity.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'package:todo/features/todo/todo.dart';

class TodoEntity {
TodoEntity({
required this.id,
Expand All @@ -11,11 +9,15 @@ class TodoEntity {
final String title;
final bool completed;

Todo toTodo() {
return Todo(
id: id,
title: title,
completed: completed,
TodoEntity copyWith({
String? id,
String? title,
bool? completed,
}) {
return TodoEntity(
id: id ?? this.id,
title: title ?? this.title,
completed: completed ?? this.completed,
);
}
}
15 changes: 7 additions & 8 deletions lib/features/todo/todo_page.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import 'package:flutter/material.dart';
import 'package:todo/features/todo/todo_entity.dart';
import 'package:todo/features/todo/todo_page_view_model.dart';
import 'package:todo/features/todo/todo_repository.dart';
import 'package:todo/shared/date_service.dart';
import 'package:todo/shared/locator.dart';
import 'package:todo/shared/ui_utilities/value_listenable_builder_x.dart';

import 'todo.dart';

class TodoPage extends StatefulWidget {
const TodoPage({super.key});

Expand Down Expand Up @@ -133,10 +132,10 @@ class TodoList extends StatelessWidget {
required this.removeTodo,
});

final ValueNotifier<List<Todo>> todosNotifier;
final ValueNotifier<List<TodoEntity>> todosNotifier;
final ValueNotifier<bool> showCompletedTodos;
final void Function(Todo todo) toggleDone;
final void Function(Todo todo) removeTodo;
final void Function(TodoEntity todo) toggleDone;
final void Function(TodoEntity todo) removeTodo;

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -170,9 +169,9 @@ class TodoItem extends StatelessWidget {
});

final ValueNotifier<bool> showCompletedTodos;
final Todo todo;
final void Function(Todo todo) toggleDone;
final void Function(Todo todo) removeTodo;
final TodoEntity todo;
final void Function(TodoEntity todo) toggleDone;
final void Function(TodoEntity todo) removeTodo;

@override
Widget build(BuildContext context) {
Expand Down
24 changes: 12 additions & 12 deletions lib/features/todo/todo_page_view_model.dart
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:todo/features/todo/todo.dart';
import 'package:todo/features/todo/todo_entity.dart';
import 'package:todo/features/todo/todo_repository.dart';
import 'package:todo/shared/date_service.dart';

/// the viewmodel which is responsible for business logic of the page
/// this should be fully unit testable and dependencies should be constructor injected
class TodoPageViewModel {
TodoPageViewModel(
{required DateService dateService,
required TodoRepository todoRepository})
: _dateService = dateService,
TodoPageViewModel({
required DateService dateService,
required TodoRepository todoRepository,
}) : _dateService = dateService,
_todoRepository = todoRepository;

final DateService _dateService;
final TodoRepository _todoRepository;

ValueNotifier<DateTime> get serviceDate => _dateService.dateNotifier;

final ValueNotifier<List<Todo>> todosNotifier = ValueNotifier([]);
final ValueNotifier<List<TodoEntity>> todosNotifier = ValueNotifier([]);
final ValueNotifier<bool> showCompletedTodosNotifier = ValueNotifier(false);

StreamSubscription<List<Todo>>? _todoListStream;
StreamSubscription<List<TodoEntity>>? _subscription;

bool get hasNonCompletedTodos =>
todosNotifier.value.where((element) => element.completed).isNotEmpty;

void init() {
_todoListStream = _todoRepository.watch().listen((todos) {
_subscription = _todoRepository.watch().listen((todos) {
todosNotifier.value = todos;
});
}
Expand All @@ -37,11 +37,11 @@ class TodoPageViewModel {
_todoRepository.addTodo(title: title);
}

Future<void> remove(Todo todo) async {
Future<void> remove(TodoEntity todo) async {
_todoRepository.removeTodo(todo);
}

Future<void> toggleDone(Todo todo) async {
Future<void> toggleDone(TodoEntity todo) async {
_todoRepository.toggleDone(todo);
}

Expand All @@ -54,7 +54,7 @@ class TodoPageViewModel {
}

void dispose() {
_todoListStream?.cancel();
_todoListStream = null;
_subscription?.cancel();
_subscription = null;
}
}
43 changes: 13 additions & 30 deletions lib/features/todo/todo_repository.dart
Original file line number Diff line number Diff line change
@@ -1,48 +1,31 @@
import 'dart:async';

import 'package:todo/fake_data_source.dart';
import 'package:todo/features/todo/todo.dart';
import 'package:todo/database.dart';
import 'package:todo/features/todo/todo_entity.dart';
import 'package:uuid/uuid.dart';

// Provide a uniform way for services and view models to interact with your data
class TodoRepository {
TodoRepository({required FakeDataSource fakeDataSource})
: _fakeDataSource = fakeDataSource;
TodoRepository({required InMemoryDataSource inMemoryDataSource})
: _inMemoryDataSource = inMemoryDataSource;

final FakeDataSource _fakeDataSource;
final BehaviorSubject<List<Todo>> _todosController = BehaviorSubject([]);
// This is an example but could be for example Firestore
final InMemoryDataSource _inMemoryDataSource;

Stream<List<Todo>> watch() {
return _todosController.stream;
Stream<List<TodoEntity>> watch() {
return _inMemoryDataSource.stream;
}

Future<void> addTodo({required String title}) async {
const uuid = Uuid();
final id = uuid.v4();
final todo = TodoEntity(id: id, title: title, completed: false);

_fakeDataSource.add();
_todosController.add([..._todosController.value, todo.toTodo()]);
_inMemoryDataSource.add(title: title);
}

Future<void> removeTodo(Todo todo) async {
_fakeDataSource.remove();

_todosController
.add(_todosController.value.where((t) => t.id != todo.id).toList());
Future<void> removeTodo(TodoEntity todo) async {
_inMemoryDataSource.remove(todo);
}

Future<void> toggleDone(Todo todo) async {
_fakeDataSource.update();

final updatedTodos = _todosController.value.map((t) {
if (t.id == todo.id) {
return Todo(id: t.id, title: t.title, completed: !t.completed);
}
return t;
}).toList();
Future<void> toggleDone(TodoEntity todo) async {
final updatedTodo = todo.copyWith(completed: !todo.completed);

_todosController.add(updatedTodos);
_inMemoryDataSource.update(updatedTodo);
}
}
3 changes: 2 additions & 1 deletion lib/shared/locator.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:get_it/get_it.dart';
import 'package:todo/database.dart';
import 'package:todo/features/todo/todo_repository.dart';
import 'package:todo/shared/date_service.dart';

Expand All @@ -9,7 +10,7 @@ void setupLocators() {

locator.registerLazySingleton<TodoRepository>(
() => TodoRepository(
fakeLocalDataSource: FakeLocalDataSource(),
inMemoryDataSource: InMemoryDataSource(),
),
);
}

0 comments on commit 31d3dea

Please sign in to comment.