Skip to content

Commit 6206598

Browse files
authored
v0.2.0 TodoProvider
1 parent 4910caf commit 6206598

File tree

7 files changed

+274
-210
lines changed

7 files changed

+274
-210
lines changed

lib/main.dart

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import 'package:flutter/material.dart';
22
import 'package:provider/provider.dart';
3-
import 'package:flutter_todo/views/loading.dart';
3+
44
import 'package:flutter_todo/providers/auth.dart';
5+
import 'package:flutter_todo/providers/todo.dart';
6+
7+
import 'package:flutter_todo/views/loading.dart';
58
import 'package:flutter_todo/views/login.dart';
69
import 'package:flutter_todo/views/register.dart';
710
import 'package:flutter_todo/views/password_reset.dart';
@@ -18,7 +21,6 @@ void main() {
1821
'/login': (context) => LogIn(),
1922
'/register': (context) => Register(),
2023
'/password-reset': (context) => PasswordReset(),
21-
'/todos': (context) => Todos(),
2224
},
2325
),
2426
),
@@ -28,6 +30,9 @@ void main() {
2830
class Router extends StatelessWidget {
2931
@override
3032
Widget build(BuildContext context) {
33+
34+
final authProvider = Provider.of<AuthProvider>(context);
35+
3136
return Consumer<AuthProvider>(
3237
builder: (context, user, child) {
3338
switch (user.status) {
@@ -36,11 +41,14 @@ class Router extends StatelessWidget {
3641
case Status.Unauthenticated:
3742
return LogIn();
3843
case Status.Authenticated:
39-
return Todos();
44+
return ChangeNotifierProvider(
45+
builder: (context) => TodoProvider(authProvider),
46+
child: Todos(),
47+
);
4048
default:
4149
return LogIn();
4250
}
4351
},
4452
);
4553
}
46-
}
54+
}

lib/providers/auth.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ class AuthProvider with ChangeNotifier {
3131
}
3232

3333
Future<bool> login(String email, String password) async {
34-
3534
_status = Status.Authenticating;
3635
_notification = '';
3736
notifyListeners();
@@ -46,9 +45,9 @@ class AuthProvider with ChangeNotifier {
4645
final response = await http.post(url, body: body,);
4746

4847
if (response.statusCode == 200) {
49-
Object apiResponse = json.decode(response.body);
48+
Map<String, dynamic> apiResponse = json.decode(response.body);
5049
_status = Status.Authenticated;
51-
print('_status updated to $_status');
50+
_token = apiResponse['access_token'];
5251
await storeUserData(apiResponse);
5352
notifyListeners();
5453
return true;

lib/providers/todo.dart

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import 'package:flutter/material.dart';
2+
3+
import 'package:flutter_todo/providers/auth.dart';
4+
import 'package:flutter_todo/utilities/api.dart';
5+
import 'package:flutter_todo/models/todo.dart';
6+
import 'package:flutter_todo/widgets/todo_response.dart';
7+
8+
class TodoProvider with ChangeNotifier {
9+
bool _initialized = false;
10+
11+
// Stores separate lists for open and closed todos.
12+
List<Todo> _openTodos = List<Todo>();
13+
List<Todo> _closedTodos = List<Todo>();
14+
15+
// The API is paginated. If there are more results we store
16+
// the API url in order to lazily load them later.
17+
String _openTodosApiMore;
18+
String _closedTodosApiMore;
19+
20+
// API Service
21+
ApiService apiService;
22+
23+
// Provides access to private variables.
24+
bool get initialized => _initialized;
25+
List<Todo> get openTodos => _openTodos;
26+
List<Todo> get closedTodos => _closedTodos;
27+
String get openTodosApiMore => _openTodosApiMore;
28+
String get closedTodosApiMore => _closedTodosApiMore;
29+
30+
// AuthProvider is required to instaniate our ApiService.
31+
// This gives the service access to the user token and provider methods.
32+
TodoProvider(AuthProvider authProvider) {
33+
this.apiService = ApiService(authProvider);
34+
init();
35+
}
36+
37+
void init() async {
38+
TodoResponse openTodosResponse = await apiService.getTodos('open');
39+
TodoResponse closedTodosResponse = await apiService.getTodos('closed');
40+
41+
_initialized = true;
42+
_openTodos = openTodosResponse.todos;
43+
_openTodosApiMore = openTodosResponse.apiMore;
44+
_closedTodos = closedTodosResponse.todos;
45+
_closedTodosApiMore = closedTodosResponse.apiMore;
46+
47+
notifyListeners();
48+
}
49+
50+
Future<bool> addTodo(String text) async {
51+
// Posts the new item to our API.
52+
bool response = await apiService.addTodo(text);
53+
54+
// If API update was successful, we add the item to _openTodos.
55+
if (response) {
56+
Todo todo = new Todo();
57+
todo.value = text;
58+
todo.status = 'open';
59+
60+
List<Todo> openTodosModified = _openTodos;
61+
openTodosModified.add(todo);
62+
63+
_openTodos = openTodosModified;
64+
notifyListeners();
65+
66+
return true;
67+
}
68+
69+
return false;
70+
}
71+
72+
Future<bool> toggleTodo(Todo todo) async {
73+
List<Todo> openTodosModified = _openTodos;
74+
List<Todo> closedTodosModified = _closedTodos;
75+
76+
// Get current status in case there's an error and it can't be set.
77+
String status = todo.status;
78+
79+
// Get the new status for the the todo.
80+
String statusModified = todo.status == 'open' ? 'closed' : 'open';
81+
82+
// Set the todo status to processing while we wait for the API call to complete.
83+
todo.status = 'processing';
84+
notifyListeners();
85+
86+
// Updates the status via an API call.
87+
bool updated = await apiService.toggleTodoStatus(todo.id, statusModified);
88+
89+
// Modify the todo with the new status.
90+
Todo modifiedTodo = todo;
91+
modifiedTodo.status = statusModified;
92+
93+
if (statusModified == 'open') {
94+
openTodosModified.add(modifiedTodo);
95+
closedTodosModified.remove(todo);
96+
}
97+
98+
if (statusModified == 'closed') {
99+
closedTodosModified.add(modifiedTodo);
100+
openTodosModified.remove(todo);
101+
}
102+
103+
if (updated) {
104+
_openTodos = openTodosModified;
105+
_closedTodos = closedTodosModified;
106+
notifyListeners();
107+
return updated;
108+
}
109+
110+
// If API update failed, we set the status back to original state.
111+
todo.status = status;
112+
notifyListeners();
113+
return updated;
114+
}
115+
116+
Future<void> loadMore(String activeTab) async {
117+
// Set apiMore based on the activeTab.
118+
String apiMore = (activeTab == 'open') ? _openTodosApiMore : _closedTodosApiMore;
119+
120+
// If there's no more items to load, return early.
121+
if (apiMore == null) {
122+
return;
123+
}
124+
125+
// Make the API call to get more todos.
126+
TodoResponse todosResponse = await apiService.getTodos(activeTab, url: apiMore);
127+
128+
// Get the current todos for the active tab.
129+
List<Todo> currentTodos = (activeTab == 'open') ? _openTodos : _closedTodos;
130+
131+
// Combine current todos with new results from API.
132+
List<Todo> allTodos = [...currentTodos, ...todosResponse.todos];
133+
134+
if (activeTab == 'open') {
135+
_openTodos = allTodos;
136+
_openTodosApiMore = todosResponse.apiMore;
137+
}
138+
139+
if (activeTab == 'closed') {
140+
_closedTodos = allTodos;
141+
_closedTodosApiMore = todosResponse.apiMore;
142+
}
143+
144+
notifyListeners();
145+
}
146+
147+
}

lib/utilities/api.dart

Lines changed: 74 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -10,85 +10,95 @@ import 'package:flutter_todo/providers/auth.dart';
1010
import 'package:flutter_todo/models/todo.dart';
1111
import 'package:flutter_todo/widgets/todo_response.dart';
1212

13-
/*
14-
* Returns a list of todos.
15-
*/
16-
Future<TodoResponse> getTodos(BuildContext context, String status, { String url = '' }) async {
17-
18-
// Defaults to the first page if no url is set.
19-
if ('' == url) {
20-
url = 'https://laravelreact.com/api/v1/todo?status=$status';
21-
}
22-
23-
String token = await Provider.of<AuthProvider>(context).getToken();
13+
class ApiService {
2414

25-
final response = await http.get(
26-
url,
27-
headers: {
28-
HttpHeaders.authorizationHeader: 'Bearer $token'
29-
},
30-
);
15+
AuthProvider authProvider;
16+
String token;
3117

32-
if (response.statusCode == 401) {
33-
await Provider.of<AuthProvider>(context).logOut(true);
34-
return TodoResponse([], null);
18+
// The AuthProvider is passed in when this class instantiated.
19+
// This provides access to the user token required for API calls.
20+
// It also allows us to log out a user when their token expires.
21+
ApiService(AuthProvider authProvider) {
22+
this.authProvider = authProvider;
23+
this.token = authProvider.token;
3524
}
3625

37-
LinkedHashMap<String, dynamic> apiResponse = json.decode(response.body);
38-
List<dynamic> data = apiResponse['data'];
39-
40-
List<Todo> todos = todoFromJson(json.encode(data));
41-
String next = apiResponse['links']['next'];
42-
43-
return TodoResponse(todos, next);
44-
}
26+
final String api = 'https://laravelreact.com/api/v1/todo';
27+
28+
/*
29+
* Returns a list of todos.
30+
*/
31+
Future<TodoResponse> getTodos(String status, { String url = '' }) async {
32+
33+
// Defaults to the first page if no url is set.
34+
if ('' == url) {
35+
url = "$api?status=$status";
36+
}
37+
38+
final response = await http.get(
39+
url,
40+
headers: {
41+
HttpHeaders.authorizationHeader: 'Bearer $token'
42+
},
43+
);
44+
45+
if (response.statusCode == 401) {
46+
await authProvider.logOut(true);
47+
return TodoResponse([], null);
48+
}
49+
50+
Map<String, dynamic> apiResponse = json.decode(response.body);
51+
List<dynamic> data = apiResponse['data'];
52+
53+
List<Todo> todos = todoFromJson(json.encode(data));
54+
String next = apiResponse['links']['next'];
55+
56+
return TodoResponse(todos, next);
57+
}
4558

46-
toggleTodoStatus(context, int id, String status) async {
47-
final url = 'https://laravelreact.com/api/v1/todo/$id';
59+
toggleTodoStatus(int id, String status) async {
60+
final url = 'https://laravelreact.com/api/v1/todo/$id';
4861

49-
String token = await Provider.of<AuthProvider>(context).getToken();
62+
Map<String, String> body = {
63+
'status': status,
64+
};
5065

51-
Map<String, String> body = {
52-
'status': status,
53-
};
66+
final response = await http.patch(
67+
url,
68+
headers: {
69+
HttpHeaders.authorizationHeader: 'Bearer $token'
70+
},
71+
body: body
72+
);
5473

55-
final response = await http.patch(
56-
url,
57-
headers: {
58-
HttpHeaders.authorizationHeader: 'Bearer $token'
59-
},
60-
body: body
61-
);
74+
if (response.statusCode == 401) {
75+
await authProvider.logOut(true);
76+
return false;
77+
}
6278

63-
if (response.statusCode == 401) {
64-
await Provider.of<AuthProvider>(context).logOut(true);
65-
return false;
79+
return true;
6680
}
6781

68-
return true;
69-
}
70-
71-
addTodo(context, String text) async {
72-
final url = 'https://laravelreact.com/api/v1/todo';
82+
addTodo(String text) async {
7383

74-
String token = await Provider.of<AuthProvider>(context).getToken();
84+
Map<String, String> body = {
85+
'value': text,
86+
};
7587

76-
Map<String, String> body = {
77-
'value': text,
78-
};
88+
final response = await http.post(
89+
api,
90+
headers: {
91+
HttpHeaders.authorizationHeader: 'Bearer $token'
92+
},
93+
body: body
94+
);
7995

80-
final response = await http.post(
81-
url,
82-
headers: {
83-
HttpHeaders.authorizationHeader: 'Bearer $token'
84-
},
85-
body: body
86-
);
96+
if (response.statusCode == 401) {
97+
await authProvider.logOut(true);
98+
return false;
99+
}
87100

88-
if (response.statusCode == 401) {
89-
await Provider.of<AuthProvider>(context).logOut(true);
90-
return false;
101+
return true;
91102
}
92103

93-
return true;
94104
}

0 commit comments

Comments
 (0)