Commit 28890e40 authored by Brian Egan's avatar Brian Egan

Use repo for every sample

parent c82f7950
......@@ -3,14 +3,18 @@ import 'dart:convert';
import 'dart:io';
import 'package:built_redux_sample/models/models.dart';
import 'package:built_redux_sample/models/serializers.dart';
import 'package:path_provider/path_provider.dart';
/// Loads and saves a List of Todos using a text file stored on the device.
class FileStorage {
final String tag;
final Future<Directory> Function() getDirectory;
const FileStorage(this.tag);
const FileStorage(
this.tag,
this.getDirectory,
);
/// LoadTodos
Future<List<Todo>> loadTodos() async {
final file = await _getLocalFile();
final contents = await file.readAsString();
......@@ -24,20 +28,18 @@ class FileStorage {
Future<File> saveTodos(List<Todo> todos) async {
final file = await _getLocalFile();
final string = JSON.encode(
serializers.serializeWith(
AppState.serializer,
new AppState.fromTodos(todos),
),
);
return file.writeAsString(
string,
JSON.encode(
serializers.serializeWith(
AppState.serializer,
new AppState.fromTodos(todos),
),
),
);
}
Future<File> _getLocalFile() async {
final dir = await getApplicationDocumentsDirectory();
final dir = await getDirectory();
return new File('${dir.path}/FlutterMvcFileStorage__$tag.json');
}
......
......@@ -3,15 +3,24 @@ import 'dart:core';
import 'package:built_redux_sample/data/file_storage.dart';
import 'package:built_redux_sample/data/web_service.dart';
import 'package:built_redux_sample/models/models.dart';
import 'package:path_provider/path_provider.dart';
/// A class that glues together our local file storage and a remote web service.
class TodosService {
/// A class that glues together our local file storage and web client. It has a
/// clear responsibility: Load Todos and Persist todos.
///
/// In most apps, we use the provided repository. In this case, it makes sense
/// to demonstrate the built_value serializers, which are used in the
/// FileStorage part of this app.
class TodosRepository {
final FileStorage fileStorage;
final WebService webService;
final WebClient webClient;
const TodosService({
this.fileStorage = const FileStorage('__built_redux__'),
this.webService = const WebService(),
const TodosRepository({
this.fileStorage = const FileStorage(
'__built_redux__',
getApplicationDocumentsDirectory,
),
this.webClient = const WebClient(),
});
/// Loads todos first from File storage. If they don't exist or encounter an
......@@ -20,7 +29,7 @@ class TodosService {
try {
return await fileStorage.loadTodos();
} catch (e) {
return webService.fetchTodos();
return webClient.fetchTodos();
}
}
......@@ -28,7 +37,7 @@ class TodosService {
Future saveTodos(List<Todo> todos) {
return Future.wait([
fileStorage.saveTodos(todos),
webService.postTodos(todos),
webClient.postTodos(todos),
]);
}
}
......@@ -6,10 +6,10 @@ import 'package:built_redux_sample/models/models.dart';
///
/// Since we're trying to keep this example simple, it doesn't communicate with
/// a real server but simply emulates the functionality.
class WebService {
class WebClient {
final Duration delay;
const WebService([this.delay = const Duration(milliseconds: 3000)]);
const WebClient([this.delay = const Duration(milliseconds: 3000)]);
/// Mock that "fetches" some Todos from a "web service" after a short delay
Future<List<Todo>> fetchTodos() async {
......
import 'package:built_redux/built_redux.dart';
import 'package:built_redux_sample/data/todos_service.dart';
import 'package:built_redux_sample/data/todos_repository.dart';
import 'package:built_redux_sample/models/models.dart';
import 'package:built_redux_sample/actions/actions.dart';
Middleware<AppState, AppStateBuilder, AppActions> createStoreTodosMiddleware([
TodosService service = const TodosService(),
TodosRepository service = const TodosRepository(),
]) {
return (new MiddlewareBuilder<AppState, AppStateBuilder, AppActions>()
..add(AppActionsNames.fetchTodosAction, createFetchTodos(service))
......@@ -20,7 +20,7 @@ Middleware<AppState, AppStateBuilder, AppActions> createStoreTodosMiddleware([
}
MiddlewareHandler<AppState, AppStateBuilder, AppActions, Null> createFetchTodos(
TodosService service) {
TodosRepository service) {
return (MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
ActionHandler next, Action<Null> action) {
if (api.state.isLoading) {
......@@ -35,7 +35,7 @@ MiddlewareHandler<AppState, AppStateBuilder, AppActions, Null> createFetchTodos(
}
MiddlewareHandler<AppState, AppStateBuilder, AppActions, T> createSaveTodos<T>(
TodosService service) {
TodosRepository service) {
return (MiddlewareApi<AppState, AppStateBuilder, AppActions> api,
ActionHandler next, Action<T> action) {
next(action);
......
import 'dart:io';
import 'package:built_redux_sample/data/file_storage.dart';
import 'package:built_redux_sample/models/todo.dart';
import 'package:test/test.dart';
main() {
group('FileStorage', () {
final todos = [new Todo("Yep")];
final directory = Directory.systemTemp.createTemp('__storage_test__');
final storage = new FileStorage(
'_test_tag_',
() => directory,
);
tearDownAll(() async {
final tempDirectory = await directory;
tempDirectory.deleteSync(recursive: true);
});
test('Should persist TodoEntities to disk', () async {
final file = await storage.saveTodos(todos);
expect(file.existsSync(), isTrue);
});
test('Should load TodoEntities from disk', () async {
final loadedTodos = await storage.loadTodos();
expect(loadedTodos, todos);
});
});
}
......@@ -2,7 +2,7 @@ import 'dart:async';
import 'package:built_redux/built_redux.dart';
import 'package:built_redux_sample/actions/actions.dart';
import 'package:built_redux_sample/data/todos_service.dart';
import 'package:built_redux_sample/data/todos_repository.dart';
import 'package:built_redux_sample/middleware/store_todos_middleware.dart';
import 'package:built_redux_sample/models/models.dart';
import 'package:built_redux_sample/reducers/reducers.dart';
......@@ -11,7 +11,7 @@ import 'package:test/test.dart';
/// We create two Mocks for our Web Service and File Storage. We will use these
/// mock classes to verify the behavior of the TodosService.
class MockTodosService extends Mock implements TodosService {}
class MockTodosService extends Mock implements TodosRepository {}
main() {
group('TodosMiddleware', () {
......
import 'dart:async';
import 'package:built_redux_sample/data/file_storage.dart';
import 'package:built_redux_sample/data/todos_service.dart';
import 'package:built_redux_sample/data/todos_repository.dart';
import 'package:built_redux_sample/data/web_service.dart';
import 'package:built_redux_sample/models/models.dart';
import 'package:mockito/mockito.dart';
......@@ -11,18 +11,18 @@ import 'package:test/test.dart';
/// mock classes to verify the behavior of the TodosService.
class MockFileStorage extends Mock implements FileStorage {}
class MockWebService extends Mock implements WebService {}
class MockWebService extends Mock implements WebClient {}
main() {
group('TodosService', () {
group('TodosRepository', () {
test(
'should load todos from File Storage if they exist without calling the web service',
() {
final fileStorage = new MockFileStorage();
final webService = new MockWebService();
final todosService = new TodosService(
final todosService = new TodosRepository(
fileStorage: fileStorage,
webService: webService,
webClient: webService,
);
final todos = [new Todo("Task")];
......@@ -40,9 +40,9 @@ main() {
() async {
final fileStorage = new MockFileStorage();
final webService = new MockWebService();
final todosService = new TodosService(
final todosService = new TodosRepository(
fileStorage: fileStorage,
webService: webService,
webClient: webService,
);
final todos = [new Todo("Task")];
......@@ -62,9 +62,9 @@ main() {
() async {
final fileStorage = new MockFileStorage();
final webService = new MockWebService();
final todosService = new TodosService(
final todosService = new TodosRepository(
fileStorage: fileStorage,
webService: webService,
webClient: webService,
);
final todos = [new Todo("Task")];
......@@ -79,9 +79,9 @@ main() {
test('should persist the todos to local disk and the web service', () {
final fileStorage = new MockFileStorage();
final webService = new MockWebService();
final todosService = new TodosService(
final todosService = new TodosRepository(
fileStorage: fileStorage,
webService: webService,
webClient: webService,
);
final todos = [new Todo("Task")];
......
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:inherited_widget_sample/models.dart';
import 'package:path_provider/path_provider.dart';
/// Loads and saves a List of Todos using a text file stored on the device.
class FileStorage {
final String tag;
const FileStorage(this.tag);
Future<List<Todo>> loadTodos() async {
final file = await _getLocalFile();
final string = await file.readAsString();
return AppState.fromJson(new JsonDecoder().convert(string)).todos;
}
Future<File> saveTodos(List<Todo> todos) async {
final file = await _getLocalFile();
return file.writeAsString(
new JsonEncoder().convert(new AppState(todos: todos).toJson()));
}
Future<File> _getLocalFile() async {
final dir = await getApplicationDocumentsDirectory();
return new File('${dir.path}/FlutterMvcFileStorage__$tag.json');
}
Future<FileSystemEntity> clean() async {
final file = await _getLocalFile();
return file.delete();
}
}
import 'dart:async';
import 'dart:core';
import 'package:inherited_widget_sample/data/file_storage.dart';
import 'package:inherited_widget_sample/data/web_service.dart';
import 'package:inherited_widget_sample/models.dart';
/// A class that glues together our local file storage and a remote web service.
class TodosService {
final FileStorage fileStorage;
final WebService webService;
const TodosService({
this.fileStorage = const FileStorage('__inherited_widget_sample__'),
this.webService = const WebService(),
});
/// Loads todos first from File storage. If they don't exist or encounter an
/// error, it attempts to load the Todos from a Web Service.
Future<List<Todo>> loadTodos() async {
try {
return await fileStorage.loadTodos();
} catch (e) {
return webService.fetchTodos();
}
}
// Persists todos to local disk and the web
Future saveTodos(List<Todo> todos) {
return Future.wait([
fileStorage.saveTodos(todos),
webService.postTodos(todos),
]);
}
}
import 'dart:async';
import 'package:inherited_widget_sample/models.dart';
/// A class that is meant to represent a Web Service you would call to fetch
/// and persist Todos to and from the cloud.
///
/// Since we're trying to keep this example simple, it doesn't communicate with
/// a real server but simply emulates the functionality.
class WebService {
final Duration delay;
const WebService([this.delay = const Duration(milliseconds: 1200)]);
/// Mock that "fetches" some Todos from a "web service" after a short delay
Future<List<Todo>> fetchTodos() async {
return new Future.delayed(
delay,
() => [
new Todo(
'Buy food for da kitty',
note: 'With the chickeny bits!',
id: '1',
),
new Todo(
'Find a Red Sea dive trip',
note: 'Echo vs MY Dream',
id: '2',
),
new Todo(
'Book flights to Egypt',
complete: true,
id: '3',
),
new Todo(
'Decide on accommodation',
id: '4',
),
new Todo(
'Sip Margaritas',
note: 'on the beach',
complete: true,
id: '5',
),
]);
}
/// Mock that returns true or false for success or failure. In this case,
/// it will "Always Succeed"
Future<bool> postTodos(List<Todo> todos) async {
return new Future.value(true);
}
}
import 'package:flutter_architecture_samples/uuid.dart';
import 'package:todos_repository/todos_repository.dart';
class AppState {
bool isLoading;
......@@ -55,26 +56,10 @@ class AppState {
todos.forEach((todo) => todo.complete = !allCompleted);
}
Map<String, Object> toJson() {
return {
"todos": todos.map((todo) => todo.toJson()).toList(),
};
}
@override
String toString() {
return 'AppState{todos: $todos, isLoading: $isLoading}';
}
static AppState fromJson(Map<String, Object> json) {
final todos = (json["todos"] as List<Map<String, Object>>)
.map((todoJson) => Todo.fromJson(todoJson))
.toList();
return new AppState(
todos: todos,
);
}
}
enum AppTab { todos, stats }
......@@ -104,26 +89,21 @@ class Todo {
note == other.note &&
id == other.id;
Map<String, Object> toJson() {
return {
"complete": complete,
"task": task,
"note": note,
"id": id,
};
}
@override
String toString() {
return 'Todo{complete: $complete, task: $task, note: $note, id: $id}';
}
static Todo fromJson(Map<String, Object> json) {
TodoEntity toEntity() {
return new TodoEntity(task, id, note, complete);
}
static Todo fromEntity(TodoEntity entity) {
return new Todo(
json["task"] as String,
complete: json["complete"] as bool,
note: json["note"] as String,
id: json["id"] as String,
entity.task,
complete: entity.complete ?? false,
note: entity.note,
id: entity.id ?? new Uuid().generateV4(),
);
}
}
......
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:inherited_widget_sample/app.dart';
import 'package:inherited_widget_sample/data/todos_service.dart';
import 'package:inherited_widget_sample/models.dart';
import 'package:todos_repository/todos_repository.dart';
import 'package:path_provider/path_provider.dart';
class StateContainerController extends StatefulWidget {
final AppState state;
final TodosService service;
final TodosRepository service;
StateContainerController({
this.service = const TodosService(),
this.service = const TodosRepository(
fileStorage: const FileStorage(
'inherited_widget_sample',
getApplicationDocumentsDirectory,
),
),
this.state,
});
......@@ -32,7 +38,9 @@ class StateContainerControllerState extends State<StateContainerController> {
widget.service.loadTodos().then((loadedTodos) {
setState(() {
state = new AppState(todos: loadedTodos);
state = new AppState(
todos: loadedTodos.map(Todo.fromEntity).toList(),
);
});
}).catchError((err) {
setState(() {
......@@ -92,7 +100,8 @@ class StateContainerControllerState extends State<StateContainerController> {
void setState(VoidCallback fn) {
super.setState(fn);
widget.service.saveTodos(state.todos);
widget.service
.saveTodos(state.todos.map((todo) => todo.toEntity()).toList());
}
@override
......
......@@ -2,6 +2,8 @@ name: inherited_widget_sample
description: A new Flutter project.
dependencies:
todos_repository:
path: ../todos_repository
flutter_architecture_samples:
path: ../../
flutter:
......
import 'package:inherited_widget_sample/data/file_storage.dart';
import 'package:inherited_widget_sample/data/todos_service.dart';
import 'package:inherited_widget_sample/data/web_service.dart';
import 'package:inherited_widget_sample/models.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
/// We create two Mocks for our Web Service and File Storage. We will use these
/// mock classes to verify the behavior of the TodosService.
class MockFileStorage extends Mock implements FileStorage {}
class MockWebService extends Mock implements WebService {}
main() {
group('TodosService', () {
test(
'should load todos from File Storage if they exist without calling the web service',
() {
final fileStorage = new MockFileStorage();
final webService = new MockWebService();
final todosService = new TodosService(
fileStorage: fileStorage,
webService: webService,
);
final todos = [new Todo("Task")];
// We'll use our mock throughout the tests to set certain conditions. In
// this first test, we want to mock out our file storage to return a
// list of Todos that we define here in our test!
when(fileStorage.loadTodos()).thenReturn(new Future.value(todos));
expect(todosService.loadTodos(), completion(todos));
verifyNever(webService.fetchTodos());
});
test(
'should fetch todos from the Web Service if the file storage throws a synchronous error',
() async {
final fileStorage = new MockFileStorage();
final webService = new MockWebService();
final todosService = new TodosService(
fileStorage: fileStorage,
webService: webService,
);
final todos = [new Todo("Task")];
// In this instance, we'll ask our Mock to throw an error. When it does,
// we expect the web service to be called instead.
when(fileStorage.loadTodos()).thenReturn("");
when(webService.fetchTodos()).thenReturn(new Future.value(todos));
// We check that the correct todos were returned, and that the
// webService.fetchTodos method was in fact called!
expect(await todosService.loadTodos(), todos);
verify(webService.fetchTodos());
});
test(
'should fetch todos from the Web Service if the File storage returns an async error',
() async {
final fileStorage = new MockFileStorage();
final webService = new MockWebService();
final todosService = new TodosService(
fileStorage: fileStorage,
webService: webService,
);
final todos = [new Todo("Task")];
when(fileStorage.loadTodos())
.thenAnswer((_) => new Future.error("Oh no"));
when(webService.fetchTodos()).thenReturn(new Future.value(todos));
expect(await todosService.loadTodos(), todos);
verify(webService.fetchTodos());
});
test('should persist the todos to local disk and the web service', () {
final fileStorage = new MockFileStorage();
final webService = new MockWebService();
final todosService = new TodosService(
fileStorage: fileStorage,
webService: webService,
);
final todos = [new Todo("Task")];
when(fileStorage.saveTodos(todos)).thenReturn(new Future.value("Cool"));
when(webService.postTodos(todos)).thenReturn(new Future.value("Beans."));
// In this case, we just want to verify we're correctly persisting to all
// the storage mechanisms we care about.
expect(todosService.saveTodos(todos), completes);
verify(fileStorage.saveTodos(todos));
verify(webService.postTodos(todos));
});
});
}
import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:path_provider/path_provider.dart';
import 'package:redux_sample/models/models.dart';
/// Loads and saves a List of Todos using a text file stored on the device.
class FileStorage {
final String tag;
const FileStorage(this.tag);
Future<List<Todo>> loadTodos() async {
final file = await _getLocalFile();
final string = await file.readAsString();
return AppState.fromJson(new JsonDecoder().convert(string)).todos;
}
Future<File> saveTodos(List<Todo> todos) async {
final file = await _getLocalFile();
return file.writeAsString(
new JsonEncoder().convert(new AppState(todos: todos).toJson()));
}
Future<File> _getLocalFile() async {
final dir = await getApplicationDocumentsDirectory();
return new File('${dir.path}/FlutterMvcFileStorage__$tag.json');
}
Future<FileSystemEntity> clean() async {
final file = await _getLocalFile();
return file.delete();
}
}
import 'dart:async';
import 'dart:core';
import 'package:redux_sample/data/file_storage.dart';
import 'package:redux_sample/data/web_service.dart';
import 'package:redux_sample/models/models.dart';
/// A class that glues together our local file storage and a remote web service.
class TodosService {
final FileStorage fileStorage;
final WebService webService;
const TodosService({
this.fileStorage = const FileStorage('__redux__'),
this.webService = const WebService(),
});
/// Loads todos first from File storage. If they don't exist or encounter an
/// error, it attempts to load the Todos from a Web Service.
Future<List<Todo>> loadTodos() async {
try {
return await fileStorage.loadTodos();
} catch (e) {
return webService.fetchTodos();
}
}
// Persists todos to local disk and the web
Future saveTodos(List<Todo> todos) {
return Future.wait([
fileStorage.saveTodos(todos),
webService.postTodos(todos),
]);
}
}
import 'dart:async';
import 'package:redux_sample/models/models.dart';
/// A class that is meant to represent a Web Service you would call to fetch
/// and persist Todos to and from the cloud.
///
/// Since we're trying to keep this example simple, it doesn't communicate with
/// a real server but simply emulates the functionality.
class WebService {
final Duration delay;
const WebService([this.delay = const Duration(milliseconds: 1200)]);
/// Mock that "fetches" some Todos from a "web service" after a short delay
Future<List<Todo>> fetchTodos() async {
return new Future.delayed(