Migrate web dashboard to null safety ()

* update dependencies

* Update to cloud_firestore 2.x.x

* run dart migrate

* Fix analyzer warnings from null safety migration

* Fix errors resulting from null safety migration

* Fix info level warnings

* run flutter format

* fix tests

* remove unused import, format
pull/929/head
John Ryan 3 years ago committed by GitHub
parent 0061b0d70d
commit 4893a24625
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -15,9 +15,9 @@ abstract class DashboardApi {
/// Manipulates [Category] data. /// Manipulates [Category] data.
abstract class CategoryApi { abstract class CategoryApi {
Future<Category> delete(String id); Future<Category?> delete(String id);
Future<Category> get(String id); Future<Category?> get(String id);
Future<Category> insert(Category category); Future<Category> insert(Category category);
@ -30,9 +30,9 @@ abstract class CategoryApi {
/// Manipulates [Entry] data. /// Manipulates [Entry] data.
abstract class EntryApi { abstract class EntryApi {
Future<Entry> delete(String categoryId, String id); Future<Entry?> delete(String categoryId, String id);
Future<Entry> get(String categoryId, String id); Future<Entry?> get(String categoryId, String id);
Future<Entry> insert(String categoryId, Entry entry); Future<Entry> insert(String categoryId, Entry entry);
@ -49,7 +49,7 @@ class Category {
String name; String name;
@JsonKey(ignore: true) @JsonKey(ignore: true)
String id; String? id;
Category(this.name); Category(this.name);
@ -76,7 +76,7 @@ class Entry {
DateTime time; DateTime time;
@JsonKey(ignore: true) @JsonKey(ignore: true)
String id; String? id;
Entry(this.value, this.time); Entry(this.value, this.time);

@ -13,15 +13,15 @@ class FirebaseDashboardApi implements DashboardApi {
@override @override
final CategoryApi categories; final CategoryApi categories;
FirebaseDashboardApi(Firestore firestore, String userId) FirebaseDashboardApi(FirebaseFirestore firestore, String userId)
: entries = FirebaseEntryApi(firestore, userId), : entries = FirebaseEntryApi(firestore, userId),
categories = FirebaseCategoryApi(firestore, userId); categories = FirebaseCategoryApi(firestore, userId);
} }
class FirebaseEntryApi implements EntryApi { class FirebaseEntryApi implements EntryApi {
final Firestore firestore; final FirebaseFirestore firestore;
final String userId; final String userId;
final CollectionReference _categoriesRef; final CollectionReference<Map<String, dynamic>> _categoriesRef;
FirebaseEntryApi(this.firestore, this.userId) FirebaseEntryApi(this.firestore, this.userId)
: _categoriesRef = firestore.collection('users/$userId/categories'); : _categoriesRef = firestore.collection('users/$userId/categories');
@ -29,10 +29,10 @@ class FirebaseEntryApi implements EntryApi {
@override @override
Stream<List<Entry>> subscribe(String categoryId) { Stream<List<Entry>> subscribe(String categoryId) {
var snapshots = var snapshots =
_categoriesRef.document(categoryId).collection('entries').snapshots(); _categoriesRef.doc(categoryId).collection('entries').snapshots();
var result = snapshots.map((querySnapshot) { var result = snapshots.map<List<Entry>>((querySnapshot) {
return querySnapshot.documents.map((snapshot) { return querySnapshot.docs.map<Entry>((snapshot) {
return Entry.fromJson(snapshot.data)..id = snapshot.documentID; return Entry.fromJson(snapshot.data())..id = snapshot.id;
}).toList(); }).toList();
}); });
@ -41,8 +41,8 @@ class FirebaseEntryApi implements EntryApi {
@override @override
Future<Entry> delete(String categoryId, String id) async { Future<Entry> delete(String categoryId, String id) async {
var document = _categoriesRef.document('$categoryId/entries/$id'); var document = _categoriesRef.doc('$categoryId/entries/$id');
var entry = await get(categoryId, document.documentID); var entry = await get(categoryId, document.id);
await document.delete(); await document.delete();
@ -52,18 +52,18 @@ class FirebaseEntryApi implements EntryApi {
@override @override
Future<Entry> insert(String categoryId, Entry entry) async { Future<Entry> insert(String categoryId, Entry entry) async {
var document = await _categoriesRef var document = await _categoriesRef
.document(categoryId) .doc(categoryId)
.collection('entries') .collection('entries')
.add(entry.toJson()); .add(entry.toJson());
return await get(categoryId, document.documentID); return await get(categoryId, document.id);
} }
@override @override
Future<List<Entry>> list(String categoryId) async { Future<List<Entry>> list(String categoryId) async {
var entriesRef = _categoriesRef.document(categoryId).collection('entries'); var entriesRef = _categoriesRef.doc(categoryId).collection('entries');
var querySnapshot = await entriesRef.getDocuments(); var querySnapshot = await entriesRef.get();
var entries = querySnapshot.documents var entries = querySnapshot.docs
.map((doc) => Entry.fromJson(doc.data)..id = doc.documentID) .map((doc) => Entry.fromJson(doc.data())..id = doc.id)
.toList(); .toList();
return entries; return entries;
@ -71,24 +71,24 @@ class FirebaseEntryApi implements EntryApi {
@override @override
Future<Entry> update(String categoryId, String id, Entry entry) async { Future<Entry> update(String categoryId, String id, Entry entry) async {
var document = _categoriesRef.document('$categoryId/entries/$id'); var document = _categoriesRef.doc('$categoryId/entries/$id');
await document.setData(entry.toJson()); await document.update(entry.toJson());
var snapshot = await document.get(); var snapshot = await document.get();
return Entry.fromJson(snapshot.data)..id = snapshot.documentID; return Entry.fromJson(snapshot.data()!)..id = snapshot.id;
} }
@override @override
Future<Entry> get(String categoryId, String id) async { Future<Entry> get(String categoryId, String id) async {
var document = _categoriesRef.document('$categoryId/entries/$id'); var document = _categoriesRef.doc('$categoryId/entries/$id');
var snapshot = await document.get(); var snapshot = await document.get();
return Entry.fromJson(snapshot.data)..id = snapshot.documentID; return Entry.fromJson(snapshot.data()!)..id = snapshot.id;
} }
} }
class FirebaseCategoryApi implements CategoryApi { class FirebaseCategoryApi implements CategoryApi {
final Firestore firestore; final FirebaseFirestore firestore;
final String userId; final String userId;
final CollectionReference _categoriesRef; final CollectionReference<Map<String, dynamic>> _categoriesRef;
FirebaseCategoryApi(this.firestore, this.userId) FirebaseCategoryApi(this.firestore, this.userId)
: _categoriesRef = firestore.collection('users/$userId/categories'); : _categoriesRef = firestore.collection('users/$userId/categories');
@ -96,9 +96,9 @@ class FirebaseCategoryApi implements CategoryApi {
@override @override
Stream<List<Category>> subscribe() { Stream<List<Category>> subscribe() {
var snapshots = _categoriesRef.snapshots(); var snapshots = _categoriesRef.snapshots();
var result = snapshots.map((querySnapshot) { var result = snapshots.map<List<Category>>((querySnapshot) {
return querySnapshot.documents.map((snapshot) { return querySnapshot.docs.map<Category>((snapshot) {
return Category.fromJson(snapshot.data)..id = snapshot.documentID; return Category.fromJson(snapshot.data())..id = snapshot.id;
}).toList(); }).toList();
}); });
@ -107,8 +107,8 @@ class FirebaseCategoryApi implements CategoryApi {
@override @override
Future<Category> delete(String id) async { Future<Category> delete(String id) async {
var document = _categoriesRef.document(id); var document = _categoriesRef.doc(id);
var categories = await get(document.documentID); var categories = await get(document.id);
await document.delete(); await document.delete();
@ -117,22 +117,22 @@ class FirebaseCategoryApi implements CategoryApi {
@override @override
Future<Category> get(String id) async { Future<Category> get(String id) async {
var document = _categoriesRef.document(id); var document = _categoriesRef.doc(id);
var snapshot = await document.get(); var snapshot = await document.get();
return Category.fromJson(snapshot.data)..id = snapshot.documentID; return Category.fromJson(snapshot.data()!)..id = snapshot.id;
} }
@override @override
Future<Category> insert(Category category) async { Future<Category> insert(Category category) async {
var document = await _categoriesRef.add(category.toJson()); var document = await _categoriesRef.add(category.toJson());
return await get(document.documentID); return await get(document.id);
} }
@override @override
Future<List<Category>> list() async { Future<List<Category>> list() async {
var querySnapshot = await _categoriesRef.getDocuments(); var querySnapshot = await _categoriesRef.get();
var categories = querySnapshot.documents var categories = querySnapshot.docs
.map((doc) => Category.fromJson(doc.data)..id = doc.documentID) .map((doc) => Category.fromJson(doc.data())..id = doc.id)
.toList(); .toList();
return categories; return categories;
@ -140,9 +140,9 @@ class FirebaseCategoryApi implements CategoryApi {
@override @override
Future<Category> update(Category category, String id) async { Future<Category> update(Category category, String id) async {
var document = _categoriesRef.document(id); var document = _categoriesRef.doc(id);
await document.setData(category.toJson()); await document.update(category.toJson());
var snapshot = await document.get(); var snapshot = await document.get();
return Category.fromJson(snapshot.data)..id = snapshot.documentID; return Category.fromJson(snapshot.data()!)..id = snapshot.id;
} }
} }

@ -5,6 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:collection/collection.dart';
import 'package:uuid/uuid.dart' as uuid; import 'package:uuid/uuid.dart' as uuid;
import 'api.dart'; import 'api.dart';
@ -30,7 +31,7 @@ class MockDashboardApi implements DashboardApi {
for (var i = 0; i < 30; i++) { for (var i = 0; i < 30; i++) {
var date = monthAgo.add(Duration(days: i)); var date = monthAgo.add(Duration(days: i));
var value = Random().nextInt(6) + 1; var value = Random().nextInt(6) + 1;
await entries.insert(category.id, Entry(value, date)); await entries.insert(category.id!, Entry(value, date));
} }
} }
} }
@ -42,20 +43,20 @@ class MockCategoryApi implements CategoryApi {
StreamController<List<Category>>.broadcast(); StreamController<List<Category>>.broadcast();
@override @override
Future<Category> delete(String id) async { Future<Category?> delete(String id) async {
var removed = _storage.remove(id); var removed = _storage.remove(id);
_emit(); _emit();
return removed; return removed;
} }
@override @override
Future<Category> get(String id) async { Future<Category?> get(String id) async {
return _storage[id]; return _storage[id];
} }
@override @override
Future<Category> insert(Category category) async { Future<Category> insert(Category category) async {
var id = uuid.Uuid().v4(); var id = const uuid.Uuid().v4();
var newCategory = Category(category.name)..id = id; var newCategory = Category(category.name)..id = id;
_storage[id] = newCategory; _storage[id] = newCategory;
_emit(); _emit();
@ -88,14 +89,14 @@ class MockEntryApi implements EntryApi {
StreamController.broadcast(); StreamController.broadcast();
@override @override
Future<Entry> delete(String categoryId, String id) async { Future<Entry?> delete(String categoryId, String id) async {
_emit(categoryId); _emit(categoryId);
return _storage.remove('$categoryId-$id'); return _storage.remove('$categoryId-$id');
} }
@override @override
Future<Entry> insert(String categoryId, Entry entry) async { Future<Entry> insert(String categoryId, Entry entry) async {
var id = uuid.Uuid().v4(); var id = const uuid.Uuid().v4();
var newEntry = Entry(entry.value, entry.time)..id = id; var newEntry = Entry(entry.value, entry.time)..id = id;
_storage['$categoryId-$id'] = newEntry; _storage['$categoryId-$id'] = newEntry;
_emit(categoryId); _emit(categoryId);
@ -104,10 +105,12 @@ class MockEntryApi implements EntryApi {
@override @override
Future<List<Entry>> list(String categoryId) async { Future<List<Entry>> list(String categoryId) async {
return _storage.keys var list = _storage.keys
.where((k) => k.startsWith(categoryId)) .where((k) => k.startsWith(categoryId))
.map((k) => _storage[k]) .map((k) => _storage[k])
.whereNotNull()
.toList(); .toList();
return list;
} }
@override @override
@ -127,14 +130,14 @@ class MockEntryApi implements EntryApi {
void _emit(String categoryId) { void _emit(String categoryId) {
var entries = _storage.keys var entries = _storage.keys
.where((k) => k.startsWith(categoryId)) .where((k) => k.startsWith(categoryId))
.map((k) => _storage[k]) .map((k) => _storage[k]!)
.toList(); .toList();
_streamController.add(_EntriesEvent(categoryId, entries)); _streamController.add(_EntriesEvent(categoryId, entries));
} }
@override @override
Future<Entry> get(String categoryId, String id) async { Future<Entry?> get(String categoryId, String id) async {
return _storage['$categoryId-$id']; return _storage['$categoryId-$id'];
} }
} }

@ -18,7 +18,7 @@ import 'pages/sign_in.dart';
/// The global state the app. /// The global state the app.
class AppState { class AppState {
final Auth auth; final Auth auth;
DashboardApi api; DashboardApi? api;
AppState(this.auth); AppState(this.auth);
} }
@ -33,19 +33,19 @@ class DashboardApp extends StatefulWidget {
static DashboardApi _mockApiBuilder(User user) => static DashboardApi _mockApiBuilder(User user) =>
MockDashboardApi()..fillWithMockData(); MockDashboardApi()..fillWithMockData();
static DashboardApi _apiBuilder(User user) => static DashboardApi _apiBuilder(User user) =>
FirebaseDashboardApi(Firestore.instance, user.uid); FirebaseDashboardApi(FirebaseFirestore.instance, user.uid);
final Auth auth; final Auth auth;
final ApiBuilder apiBuilder; final ApiBuilder apiBuilder;
/// Runs the app using Firebase /// Runs the app using Firebase
DashboardApp.firebase({Key key}) DashboardApp.firebase({Key? key})
: auth = FirebaseAuthService(), : auth = FirebaseAuthService(),
apiBuilder = _apiBuilder, apiBuilder = _apiBuilder,
super(key: key); super(key: key);
/// Runs the app using mock data /// Runs the app using mock data
DashboardApp.mock({Key key}) DashboardApp.mock({Key? key})
: auth = MockAuthService(), : auth = MockAuthService(),
apiBuilder = _mockApiBuilder, apiBuilder = _mockApiBuilder,
super(key: key); super(key: key);
@ -55,7 +55,7 @@ class DashboardApp extends StatefulWidget {
} }
class _DashboardAppState extends State<DashboardApp> { class _DashboardAppState extends State<DashboardApp> {
AppState _appState; late final AppState _appState;
@override @override
void initState() { void initState() {
@ -80,13 +80,13 @@ class _DashboardAppState extends State<DashboardApp> {
/// Switches between showing the [SignInPage] or [HomePage], depending on /// Switches between showing the [SignInPage] or [HomePage], depending on
/// whether or not the user is signed in. /// whether or not the user is signed in.
class SignInSwitcher extends StatefulWidget { class SignInSwitcher extends StatefulWidget {
final AppState appState; final AppState? appState;
final ApiBuilder apiBuilder; final ApiBuilder? apiBuilder;
const SignInSwitcher({ const SignInSwitcher({
this.appState, this.appState,
this.apiBuilder, this.apiBuilder,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -107,14 +107,14 @@ class _SignInSwitcherState extends State<SignInSwitcher> {
onSignOut: _handleSignOut, onSignOut: _handleSignOut,
) )
: SignInPage( : SignInPage(
auth: widget.appState.auth, auth: widget.appState!.auth,
onSuccess: _handleSignIn, onSuccess: _handleSignIn,
), ),
); );
} }
void _handleSignIn(User user) { void _handleSignIn(User user) {
widget.appState.api = widget.apiBuilder(user); widget.appState!.api = widget.apiBuilder!(user);
setState(() { setState(() {
_isSignedIn = true; _isSignedIn = true;
@ -122,7 +122,7 @@ class _SignInSwitcherState extends State<SignInSwitcher> {
} }
Future _handleSignOut() async { Future _handleSignOut() async {
await widget.appState.auth.signOut(); await widget.appState!.auth.signOut();
setState(() { setState(() {
_isSignedIn = false; _isSignedIn = false;
}); });

@ -2,7 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a // for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file. // BSD-style license that can be found in the LICENSE file.
import 'package:firebase_auth/firebase_auth.dart' hide FirebaseUser; import 'package:firebase_auth/firebase_auth.dart' hide User;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:google_sign_in/google_sign_in.dart'; import 'package:google_sign_in/google_sign_in.dart';
@ -25,21 +25,21 @@ class FirebaseAuthService implements Auth {
} }
Future<User> _signIn() async { Future<User> _signIn() async {
GoogleSignInAccount googleUser; GoogleSignInAccount? googleUser;
if (await isSignedIn) { if (await isSignedIn) {
googleUser = await _googleSignIn.signInSilently(); googleUser = await _googleSignIn.signInSilently();
} else { } else {
googleUser = await _googleSignIn.signIn(); googleUser = await _googleSignIn.signIn();
} }
var googleAuth = await googleUser.authentication; var googleAuth = await googleUser!.authentication;
var credential = GoogleAuthProvider.getCredential( var credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken, idToken: googleAuth.idToken); accessToken: googleAuth.accessToken, idToken: googleAuth.idToken);
var authResult = await _auth.signInWithCredential(credential); var authResult = await _auth.signInWithCredential(credential);
return _FirebaseUser(authResult.user.uid); return _FirebaseUser(authResult.user!.uid);
} }
@override @override

@ -2,8 +2,6 @@
// for details. All rights reserved. Use of this source code is governed by a // for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file. // BSD-style license that can be found in the LICENSE file.
import 'dart:math';
import 'auth.dart'; import 'auth.dart';
class MockAuthService implements Auth { class MockAuthService implements Auth {
@ -12,11 +10,6 @@ class MockAuthService implements Auth {
@override @override
Future<User> signIn() async { Future<User> signIn() async {
// Sign in will randomly fail 25% of the time.
var random = Random();
if (random.nextInt(4) == 0) {
throw SignInException();
}
return MockUser(); return MockUser();
} }

@ -10,13 +10,13 @@ import '../app.dart';
import '../widgets/category_chart.dart'; import '../widgets/category_chart.dart';
class DashboardPage extends StatelessWidget { class DashboardPage extends StatelessWidget {
const DashboardPage({Key key}) : super(key: key); const DashboardPage({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var appState = Provider.of<AppState>(context); var appState = Provider.of<AppState>(context);
return FutureBuilder<List<Category>>( return FutureBuilder<List<Category>>(
future: appState.api.categories.list(), future: appState.api!.categories.list(),
builder: (context, futureSnapshot) { builder: (context, futureSnapshot) {
if (!futureSnapshot.hasData) { if (!futureSnapshot.hasData) {
return const Center( return const Center(
@ -25,7 +25,7 @@ class DashboardPage extends StatelessWidget {
} }
return StreamBuilder<List<Category>>( return StreamBuilder<List<Category>>(
initialData: futureSnapshot.data, initialData: futureSnapshot.data,
stream: appState.api.categories.subscribe(), stream: appState.api!.categories.subscribe(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.data == null) { if (snapshot.data == null) {
return const Center( return const Center(
@ -41,9 +41,9 @@ class DashboardPage extends StatelessWidget {
} }
class Dashboard extends StatelessWidget { class Dashboard extends StatelessWidget {
final List<Category> categories; final List<Category>? categories;
const Dashboard(this.categories, {Key key}) : super(key: key); const Dashboard(this.categories, {Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -55,7 +55,7 @@ class Dashboard extends StatelessWidget {
maxCrossAxisExtent: 500, maxCrossAxisExtent: 500,
), ),
children: [ children: [
...categories.map( ...categories!.map(
(category) => Card( (category) => Card(
child: CategoryChart( child: CategoryChart(
category: category, category: category,

@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a // for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file. // BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart' as intl; import 'package:intl/intl.dart' as intl;
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -12,14 +14,14 @@ import '../widgets/categories_dropdown.dart';
import '../widgets/dialogs.dart'; import '../widgets/dialogs.dart';
class EntriesPage extends StatefulWidget { class EntriesPage extends StatefulWidget {
const EntriesPage({Key key}) : super(key: key); const EntriesPage({Key? key}) : super(key: key);
@override @override
_EntriesPageState createState() => _EntriesPageState(); _EntriesPageState createState() => _EntriesPageState();
} }
class _EntriesPageState extends State<EntriesPage> { class _EntriesPageState extends State<EntriesPage> {
Category _selected; Category? _selected;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -27,14 +29,14 @@ class _EntriesPageState extends State<EntriesPage> {
return Column( return Column(
children: [ children: [
CategoryDropdown( CategoryDropdown(
api: appState.api.categories, api: appState.api!.categories,
onSelected: (category) => setState(() => _selected = category)), onSelected: (category) => setState(() => _selected = category)),
Expanded( Expanded(
child: _selected == null child: _selected == null
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: EntriesList( : EntriesList(
category: _selected, category: _selected,
api: appState.api.entries, api: appState.api!.entries,
), ),
), ),
], ],
@ -43,13 +45,13 @@ class _EntriesPageState extends State<EntriesPage> {
} }
class EntriesList extends StatefulWidget { class EntriesList extends StatefulWidget {
final Category category; final Category? category;
final EntryApi api; final EntryApi api;
EntriesList({ EntriesList({
@required this.category, this.category,
@required this.api, required this.api,
}) : super(key: ValueKey(category.id)); }) : super(key: ValueKey(category?.id));
@override @override
_EntriesListState createState() => _EntriesListState(); _EntriesListState createState() => _EntriesListState();
@ -63,14 +65,14 @@ class _EntriesListState extends State<EntriesList> {
} }
return FutureBuilder<List<Entry>>( return FutureBuilder<List<Entry>>(
future: widget.api.list(widget.category.id), future: widget.api.list(widget.category!.id!),
builder: (context, futureSnapshot) { builder: (context, futureSnapshot) {
if (!futureSnapshot.hasData) { if (!futureSnapshot.hasData) {
return _buildLoadingIndicator(); return _buildLoadingIndicator();
} }
return StreamBuilder<List<Entry>>( return StreamBuilder<List<Entry>>(
initialData: futureSnapshot.data, initialData: futureSnapshot.data,
stream: widget.api.subscribe(widget.category.id), stream: widget.api.subscribe(widget.category!.id!),
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData) { if (!snapshot.hasData) {
return _buildLoadingIndicator(); return _buildLoadingIndicator();
@ -79,10 +81,10 @@ class _EntriesListState extends State<EntriesList> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
return EntryTile( return EntryTile(
category: widget.category, category: widget.category,
entry: snapshot.data[index], entry: snapshot.data![index],
); );
}, },
itemCount: snapshot.data.length, itemCount: snapshot.data!.length,
); );
}, },
); );
@ -96,20 +98,20 @@ class _EntriesListState extends State<EntriesList> {
} }
class EntryTile extends StatelessWidget { class EntryTile extends StatelessWidget {
final Category category; final Category? category;
final Entry entry; final Entry? entry;
const EntryTile({ const EntryTile({
this.category, this.category,
this.entry, this.entry,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return ListTile(
title: Text(entry.value.toString()), title: Text(entry!.value.toString()),
subtitle: Text(intl.DateFormat('MM/dd/yy h:mm a').format(entry.time)), subtitle: Text(intl.DateFormat('MM/dd/yy h:mm a').format(entry!.time)),
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -127,7 +129,7 @@ class EntryTile extends StatelessWidget {
TextButton( TextButton(
child: const Text('Delete'), child: const Text('Delete'),
onPressed: () async { onPressed: () async {
var shouldDelete = await showDialog<bool>( var shouldDelete = await (showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Delete entry?'), title: const Text('Delete entry?'),
@ -142,12 +144,12 @@ class EntryTile extends StatelessWidget {
), ),
], ],
), ),
); ) as FutureOr<bool>);
if (shouldDelete) { if (shouldDelete) {
await Provider.of<AppState>(context, listen: false) await Provider.of<AppState>(context, listen: false)
.api .api!
.entries .entries
.delete(category.id, entry.id); .delete(category!.id!, entry!.id!);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(

@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a // for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file. // BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../widgets/dialogs.dart'; import '../widgets/dialogs.dart';
@ -13,8 +15,8 @@ class HomePage extends StatefulWidget {
final VoidCallback onSignOut; final VoidCallback onSignOut;
const HomePage({ const HomePage({
@required this.onSignOut, required this.onSignOut,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -86,7 +88,7 @@ class _HomePageState extends State<HomePage> {
} }
Future<void> _handleSignOut() async { Future<void> _handleSignOut() async {
var shouldSignOut = await showDialog<bool>( var shouldSignOut = await (showDialog<bool>(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Are you sure you want to sign out?'), title: const Text('Are you sure you want to sign out?'),
@ -105,9 +107,9 @@ class _HomePageState extends State<HomePage> {
), ),
], ],
), ),
); ));
if (!shouldSignOut) { if (shouldSignOut == null || !shouldSignOut) {
return; return;
} }

@ -11,9 +11,9 @@ class SignInPage extends StatelessWidget {
final ValueChanged<User> onSuccess; final ValueChanged<User> onSuccess;
const SignInPage({ const SignInPage({
@required this.auth, required this.auth,
@required this.onSuccess, required this.onSuccess,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -31,9 +31,9 @@ class SignInButton extends StatefulWidget {
final ValueChanged<User> onSuccess; final ValueChanged<User> onSuccess;
const SignInButton({ const SignInButton({
@required this.auth, required this.auth,
@required this.onSuccess, required this.onSuccess,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -41,7 +41,7 @@ class SignInButton extends StatefulWidget {
} }
class _SignInButtonState extends State<SignInButton> { class _SignInButtonState extends State<SignInButton> {
Future<bool> _checkSignInFuture; Future<bool>? _checkSignInFuture;
@override @override
void initState() { void initState() {

@ -15,14 +15,14 @@ class EntryTotal {
/// Returns a list of [EntryTotal] objects. Each [EntryTotal] is the sum of /// Returns a list of [EntryTotal] objects. Each [EntryTotal] is the sum of
/// the values of all the entries on a given day. /// the values of all the entries on a given day.
List<EntryTotal> entryTotalsByDay(List<Entry> entries, int daysAgo, List<EntryTotal> entryTotalsByDay(List<Entry>? entries, int daysAgo,
{DateTime today}) { {DateTime? today}) {
today ??= DateTime.now(); today ??= DateTime.now();
return _entryTotalsByDay(entries, daysAgo, today).toList(); return _entryTotalsByDay(entries, daysAgo, today).toList();
} }
Iterable<EntryTotal> _entryTotalsByDay( Iterable<EntryTotal> _entryTotalsByDay(
List<Entry> entries, int daysAgo, DateTime today) sync* { List<Entry>? entries, int daysAgo, DateTime today) sync* {
var start = today.subtract(Duration(days: daysAgo)); var start = today.subtract(Duration(days: daysAgo));
var entriesByDay = _entriesInRange(start, today, entries); var entriesByDay = _entriesInRange(start, today, entries);
@ -42,18 +42,18 @@ Iterable<EntryTotal> _entryTotalsByDay(
/// lists. The outer list represents the number of days since [start], and the /// lists. The outer list represents the number of days since [start], and the
/// inner list is the group of entries on that day. /// inner list is the group of entries on that day.
List<List<Entry>> _entriesInRange( List<List<Entry>> _entriesInRange(
DateTime start, DateTime end, List<Entry> entries) => DateTime start, DateTime end, List<Entry>? entries) =>
_entriesInRangeImpl(start, end, entries).toList(); _entriesInRangeImpl(start, end, entries).toList();
Iterable<List<Entry>> _entriesInRangeImpl( Iterable<List<Entry>> _entriesInRangeImpl(
DateTime start, DateTime end, List<Entry> entries) sync* { DateTime start, DateTime end, List<Entry>? entries) sync* {
start = start.atMidnight; start = start.atMidnight;
end = end.atMidnight; end = end.atMidnight;
var d = start; var d = start;
while (d.compareTo(end) <= 0) { while (d.compareTo(end) <= 0) {
var es = <Entry>[]; var es = <Entry>[];
for (var entry in entries) { for (var entry in entries!) {
if (d.isSameDay(entry.time.atMidnight)) { if (d.isSameDay(entry.time.atMidnight)) {
es.add(entry); es.add(entry);
} }

@ -11,12 +11,12 @@ import '../api/api.dart';
/// one. /// one.
class CategoryDropdown extends StatefulWidget { class CategoryDropdown extends StatefulWidget {
final CategoryApi api; final CategoryApi api;
final ValueChanged<Category> onSelected; final ValueChanged<Category?> onSelected;
const CategoryDropdown({ const CategoryDropdown({
@required this.api, required this.api,
@required this.onSelected, required this.onSelected,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -24,9 +24,9 @@ class CategoryDropdown extends StatefulWidget {
} }
class _CategoryDropdownState extends State<CategoryDropdown> { class _CategoryDropdownState extends State<CategoryDropdown> {
Category _selected; Category? _selected;
Future<List<Category>> _future; Future<List<Category>>? _future;
Stream<List<Category>> _stream; Stream<List<Category>>? _stream;
@override @override
void initState() { void initState() {
@ -78,7 +78,7 @@ class _CategoryDropdownState extends State<CategoryDropdown> {
initialData: futureSnapshot.hasData ? futureSnapshot.data : [], initialData: futureSnapshot.hasData ? futureSnapshot.data : [],
stream: _stream, stream: _stream,
builder: (context, snapshot) { builder: (context, snapshot) {
var data = snapshot.hasData ? snapshot.data : <Category>[]; var data = snapshot.hasData ? snapshot.data! : <Category>[];
return DropdownButton<Category>( return DropdownButton<Category>(
value: _selected, value: _selected,
items: data.map(_buildDropdownItem).toList(), items: data.map(_buildDropdownItem).toList(),
@ -92,7 +92,7 @@ class _CategoryDropdownState extends State<CategoryDropdown> {
); );
} }
void _setSelected(Category category) { void _setSelected(Category? category) {
if (_selected == category) { if (_selected == category) {
return; return;
} }

@ -15,12 +15,12 @@ const _daysBefore = 10;
class CategoryChart extends StatelessWidget { class CategoryChart extends StatelessWidget {
final Category category; final Category category;
final DashboardApi api; final DashboardApi? api;
const CategoryChart({ const CategoryChart({
@required this.category, required this.category,
@required this.api, required this.api,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -51,14 +51,14 @@ class CategoryChart extends StatelessWidget {
// Load the initial snapshot using a FutureBuilder, and subscribe to // Load the initial snapshot using a FutureBuilder, and subscribe to
// additional updates with a StreamBuilder. // additional updates with a StreamBuilder.
child: FutureBuilder<List<Entry>>( child: FutureBuilder<List<Entry>>(
future: api.entries.list(category.id), future: api!.entries.list(category.id!),
builder: (context, futureSnapshot) { builder: (context, futureSnapshot) {
if (!futureSnapshot.hasData) { if (!futureSnapshot.hasData) {
return _buildLoadingIndicator(); return _buildLoadingIndicator();
} }
return StreamBuilder<List<Entry>>( return StreamBuilder<List<Entry>>(
initialData: futureSnapshot.data, initialData: futureSnapshot.data,
stream: api.entries.subscribe(category.id), stream: api!.entries.subscribe(category.id!),
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData) { if (!snapshot.hasData) {
return _buildLoadingIndicator(); return _buildLoadingIndicator();
@ -74,12 +74,14 @@ class CategoryChart extends StatelessWidget {
} }
Widget _buildLoadingIndicator() { Widget _buildLoadingIndicator() {
return const Center(child: CircularProgressIndicator()); return const Center(
child: CircularProgressIndicator(),
);
} }
} }
class _BarChart extends StatelessWidget { class _BarChart extends StatelessWidget {
final List<Entry> entries; final List<Entry>? entries;
const _BarChart({this.entries}); const _BarChart({this.entries});
@ -96,14 +98,10 @@ class _BarChart extends StatelessWidget {
id: 'Entries', id: 'Entries',
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault, colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
domainFn: (entryTotal, _) { domainFn: (entryTotal, _) {
if (entryTotal == null) return null;
var format = intl.DateFormat.Md(); var format = intl.DateFormat.Md();
return format.format(entryTotal.day); return format.format(entryTotal.day);
}, },
measureFn: (total, _) { measureFn: (total, _) {
if (total == null) return null;
return total.value; return total.value;
}, },
data: utils.entryTotalsByDay(entries, _daysBefore), data: utils.entryTotalsByDay(entries, _daysBefore),

@ -8,7 +8,7 @@ import 'package:web_dashboard/src/api/api.dart';
import 'package:web_dashboard/src/app.dart'; import 'package:web_dashboard/src/app.dart';
class NewCategoryForm extends StatefulWidget { class NewCategoryForm extends StatefulWidget {
const NewCategoryForm({Key key}) : super(key: key); const NewCategoryForm({Key? key}) : super(key: key);
@override @override
_NewCategoryFormState createState() => _NewCategoryFormState(); _NewCategoryFormState createState() => _NewCategoryFormState();
@ -24,7 +24,7 @@ class _NewCategoryFormState extends State<NewCategoryForm> {
category: _category, category: _category,
onDone: (shouldInsert) { onDone: (shouldInsert) {
if (shouldInsert) { if (shouldInsert) {
api.categories.insert(_category); api!.categories.insert(_category);
} }
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -37,9 +37,9 @@ class EditCategoryForm extends StatefulWidget {
final ValueChanged<bool> onDone; final ValueChanged<bool> onDone;
const EditCategoryForm({ const EditCategoryForm({
@required this.category, required this.category,
@required this.onDone, required this.onDone,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -67,7 +67,7 @@ class _EditCategoryFormState extends State<EditCategoryForm> {
widget.category.name = newValue; widget.category.name = newValue;
}, },
validator: (value) { validator: (value) {
if (value.isEmpty) { if (value!.isEmpty) {
return 'Please enter a name'; return 'Please enter a name';
} }
return null; return null;
@ -91,7 +91,7 @@ class _EditCategoryFormState extends State<EditCategoryForm> {
child: ElevatedButton( child: ElevatedButton(
child: const Text('OK'), child: const Text('OK'),
onPressed: () { onPressed: () {
if (_formKey.currentState.validate()) { if (_formKey.currentState!.validate()) {
widget.onDone(true); widget.onDone(true);
} }
}, },

@ -11,7 +11,7 @@ import '../app.dart';
import 'edit_entry.dart'; import 'edit_entry.dart';
class NewCategoryDialog extends StatelessWidget { class NewCategoryDialog extends StatelessWidget {
const NewCategoryDialog({Key key}) : super(key: key); const NewCategoryDialog({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -28,8 +28,8 @@ class EditCategoryDialog extends StatelessWidget {
final Category category; final Category category;
const EditCategoryDialog({ const EditCategoryDialog({
@required this.category, required this.category,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -43,7 +43,7 @@ class EditCategoryDialog extends StatelessWidget {
category: category, category: category,
onDone: (shouldUpdate) { onDone: (shouldUpdate) {
if (shouldUpdate) { if (shouldUpdate) {
api.categories.update(category, category.id); api!.categories.update(category, category.id!);
} }
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -54,7 +54,7 @@ class EditCategoryDialog extends StatelessWidget {
} }
class NewEntryDialog extends StatefulWidget { class NewEntryDialog extends StatefulWidget {
const NewEntryDialog({Key key}) : super(key: key); const NewEntryDialog({Key? key}) : super(key: key);
@override @override
_NewEntryDialogState createState() => _NewEntryDialogState(); _NewEntryDialogState createState() => _NewEntryDialogState();
@ -73,13 +73,13 @@ class _NewEntryDialogState extends State<NewEntryDialog> {
} }
class EditEntryDialog extends StatelessWidget { class EditEntryDialog extends StatelessWidget {
final Category category; final Category? category;
final Entry entry; final Entry? entry;
const EditEntryDialog({ const EditEntryDialog({
this.category, this.category,
this.entry, this.entry,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -93,7 +93,7 @@ class EditEntryDialog extends StatelessWidget {
entry: entry, entry: entry,
onDone: (shouldUpdate) { onDone: (shouldUpdate) {
if (shouldUpdate) { if (shouldUpdate) {
api.entries.update(category.id, entry.id, entry); api!.entries.update(category!.id!, entry!.id!, entry!);
} }
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },

@ -11,19 +11,19 @@ import '../app.dart';
import 'categories_dropdown.dart'; import 'categories_dropdown.dart';
class NewEntryForm extends StatefulWidget { class NewEntryForm extends StatefulWidget {
const NewEntryForm({Key key}) : super(key: key); const NewEntryForm({Key? key}) : super(key: key);
@override @override
_NewEntryFormState createState() => _NewEntryFormState(); _NewEntryFormState createState() => _NewEntryFormState();
} }
class _NewEntryFormState extends State<NewEntryForm> { class _NewEntryFormState extends State<NewEntryForm> {
Category _selected; late Category _selected;
final Entry _entry = Entry(0, DateTime.now()); final Entry _entry = Entry(0, DateTime.now());
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var api = Provider.of<AppState>(context).api; var api = Provider.of<AppState>(context).api!;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -33,6 +33,7 @@ class _NewEntryFormState extends State<NewEntryForm> {
child: CategoryDropdown( child: CategoryDropdown(
api: api.categories, api: api.categories,
onSelected: (category) { onSelected: (category) {
if (category == null) return;
setState(() { setState(() {
_selected = category; _selected = category;
}); });
@ -43,7 +44,7 @@ class _NewEntryFormState extends State<NewEntryForm> {
entry: _entry, entry: _entry,
onDone: (shouldInsert) { onDone: (shouldInsert) {
if (shouldInsert) { if (shouldInsert) {
api.entries.insert(_selected.id, _entry); api.entries.insert(_selected.id!, _entry);
} }
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -54,13 +55,13 @@ class _NewEntryFormState extends State<NewEntryForm> {
} }
class EditEntryForm extends StatefulWidget { class EditEntryForm extends StatefulWidget {
final Entry entry; final Entry? entry;
final ValueChanged<bool> onDone; final ValueChanged<bool> onDone;
const EditEntryForm({ const EditEntryForm({
@required this.entry, required this.entry,
@required this.onDone, required this.onDone,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -80,19 +81,19 @@ class _EditEntryFormState extends State<EditEntryForm> {
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: TextFormField( child: TextFormField(
initialValue: widget.entry.value.toString(), initialValue: widget.entry!.value.toString(),
decoration: const InputDecoration(labelText: 'Value'), decoration: const InputDecoration(labelText: 'Value'),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
validator: (value) { validator: (value) {
try { try {
int.parse(value); int.parse(value!);
} catch (e) { } catch (e) {
return "Please enter a whole number"; return "Please enter a whole number";
} }
return null; return null;
}, },
onChanged: (newValue) { onChanged: (newValue) {
widget.entry.value = int.parse(newValue); widget.entry!.value = int.parse(newValue);
}, },
), ),
), ),
@ -101,13 +102,13 @@ class _EditEntryFormState extends State<EditEntryForm> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(intl.DateFormat('MM/dd/yyyy').format(widget.entry.time)), Text(intl.DateFormat('MM/dd/yyyy').format(widget.entry!.time)),
ElevatedButton( ElevatedButton(
child: const Text('Edit'), child: const Text('Edit'),
onPressed: () async { onPressed: () async {
var result = await showDatePicker( var result = await showDatePicker(
context: context, context: context,
initialDate: widget.entry.time, initialDate: widget.entry!.time,
firstDate: firstDate:
DateTime.now().subtract(const Duration(days: 365)), DateTime.now().subtract(const Duration(days: 365)),
lastDate: DateTime.now()); lastDate: DateTime.now());
@ -115,7 +116,7 @@ class _EditEntryFormState extends State<EditEntryForm> {
return; return;
} }
setState(() { setState(() {
widget.entry.time = result; widget.entry!.time = result;
}); });
}, },
) )
@ -139,7 +140,7 @@ class _EditEntryFormState extends State<EditEntryForm> {
child: ElevatedButton( child: ElevatedButton(
child: const Text('OK'), child: const Text('OK'),
onPressed: () { onPressed: () {
if (_formKey.currentState.validate()) { if (_formKey.currentState!.validate()) {
widget.onDone(true); widget.onDone(true);
} }
}, },

@ -18,8 +18,8 @@ class AdaptiveScaffoldDestination {
final IconData icon; final IconData icon;
const AdaptiveScaffoldDestination({ const AdaptiveScaffoldDestination({
@required this.title, required this.title,
@required this.icon, required this.icon,
}); });
} }
@ -27,23 +27,23 @@ class AdaptiveScaffoldDestination {
/// [NavigationRail], or [BottomNavigationBar]. Navigation destinations are /// [NavigationRail], or [BottomNavigationBar]. Navigation destinations are
/// defined in the [destinations] parameter. /// defined in the [destinations] parameter.
class AdaptiveScaffold extends StatefulWidget { class AdaptiveScaffold extends StatefulWidget {
final Widget title; final Widget? title;
final List<Widget> actions; final List<Widget> actions;
final Widget body; final Widget? body;
final int currentIndex; final int currentIndex;
final List<AdaptiveScaffoldDestination> destinations; final List<AdaptiveScaffoldDestination> destinations;
final ValueChanged<int> onNavigationIndexChange; final ValueChanged<int>? onNavigationIndexChange;
final FloatingActionButton floatingActionButton; final FloatingActionButton? floatingActionButton;
const AdaptiveScaffold({ const AdaptiveScaffold({
this.title, this.title,
this.body, this.body,
this.actions = const [], this.actions = const [],
@required this.currentIndex, required this.currentIndex,
@required this.destinations, required this.destinations,
this.onNavigationIndexChange, this.onNavigationIndexChange,
this.floatingActionButton, this.floatingActionButton,
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@ -122,7 +122,7 @@ class _AdaptiveScaffoldState extends State<AdaptiveScaffold> {
color: Colors.grey[300], color: Colors.grey[300],
), ),
Expanded( Expanded(
child: widget.body, child: widget.body!,
), ),
], ],
), ),
@ -155,7 +155,7 @@ class _AdaptiveScaffoldState extends State<AdaptiveScaffold> {
void _destinationTapped(AdaptiveScaffoldDestination destination) { void _destinationTapped(AdaptiveScaffoldDestination destination) {
var idx = widget.destinations.indexOf(destination); var idx = widget.destinations.indexOf(destination);
if (idx != widget.currentIndex) { if (idx != widget.currentIndex) {
widget.onNavigationIndexChange(idx); widget.onNavigationIndexChange!(idx);
} }
} }
} }

@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared name: _fe_analyzer_shared
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "14.0.0" version: "28.0.0"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.41.2" version: "2.5.0"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -28,7 +28,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.1" version: "2.8.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -42,42 +42,42 @@ packages:
name: build name: build
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.6.2" version: "2.1.1"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
name: build_config name: build_config
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.5" version: "1.0.0"
build_daemon: build_daemon:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.10" version: "3.0.1"
build_resolvers: build_resolvers:
dependency: transitive dependency: transitive
description: description:
name: build_resolvers name: build_resolvers
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.3" version: "2.0.4"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.11.1+1" version: "2.1.4"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.7" version: "7.2.2"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -98,7 +98,7 @@ packages:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -117,8 +117,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: charts_flutter path: charts_flutter
ref: HEAD ref: "03c2aa67d12952e7f3cbba1d87646d96a9a7cdc4"
resolved-ref: "30477090290b348ed3101bc13017aae465f59017" resolved-ref: "03c2aa67d12952e7f3cbba1d87646d96a9a7cdc4"
url: "git://github.com/google/charts.git" url: "git://github.com/google/charts.git"
source: git source: git
version: "0.11.0" version: "0.11.0"
@ -128,7 +128,7 @@ packages:
name: checked_yaml name: checked_yaml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "2.0.1"
cli_util: cli_util:
dependency: transitive dependency: transitive
description: description:
@ -149,28 +149,28 @@ packages:
name: cloud_firestore name: cloud_firestore
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.13.7" version: "2.5.3"
cloud_firestore_platform_interface: cloud_firestore_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: cloud_firestore_platform_interface name: cloud_firestore_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.2" version: "5.4.2"
cloud_firestore_web: cloud_firestore_web:
dependency: transitive dependency: transitive
description: description:
name: cloud_firestore_web name: cloud_firestore_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.1+2" version: "2.4.3"
code_builder: code_builder:
dependency: transitive dependency: transitive
description: description:
name: code_builder name: code_builder
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.7.0" version: "4.1.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -184,28 +184,28 @@ packages:
name: convert name: convert
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "3.0.1"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
name: crypto name: crypto
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.5" version: "3.0.1"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
name: cupertino_icons name: cupertino_icons
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.3" version: "1.0.3"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.12" version: "2.2.0"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -219,56 +219,49 @@ packages:
name: file name: file
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.2.1" version: "6.1.2"
firebase:
dependency: transitive
description:
name: firebase
url: "https://pub.dartlang.org"
source: hosted
version: "7.3.3"
firebase_auth: firebase_auth:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_auth name: firebase_auth
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.16.1" version: "3.1.3"
firebase_auth_platform_interface: firebase_auth_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_auth_platform_interface name: firebase_auth_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.8" version: "6.1.1"
firebase_auth_web: firebase_auth_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_auth_web name: firebase_auth_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.3+1" version: "3.1.2"
firebase_core: firebase_core:
dependency: "direct main" dependency: "direct main"
description: description:
name: firebase_core name: firebase_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.4" version: "1.7.0"
firebase_core_platform_interface: firebase_core_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_platform_interface name: firebase_core_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "4.0.1"
firebase_core_web: firebase_core_web:
dependency: transitive dependency: transitive
description: description:
name: firebase_core_web name: firebase_core_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.1+2" version: "1.1.0"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -298,13 +291,20 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
glob: glob:
dependency: transitive dependency: transitive
description: description:
name: glob name: glob
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "2.0.2"
google_sign_in: google_sign_in:
dependency: "direct main" dependency: "direct main"
description: description:
@ -332,35 +332,28 @@ packages:
name: graphs name: graphs
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" version: "2.1.0"
grinder: grinder:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: grinder name: grinder
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.8.6" version: "0.9.0"
http:
dependency: transitive
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.2"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
name: http_multi_server name: http_multi_server
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.0" version: "3.0.1"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.4" version: "4.0.0"
intl: intl:
dependency: transitive dependency: transitive
description: description:
@ -374,7 +367,7 @@ packages:
name: io name: io
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.5" version: "1.0.3"
js: js:
dependency: transitive dependency: transitive
description: description:
@ -388,14 +381,14 @@ packages:
name: json_annotation name: json_annotation
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.1" version: "4.1.0"
json_serializable: json_serializable:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: json_serializable name: json_serializable
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.5.1" version: "5.0.2"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@ -416,7 +409,7 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.10" version: "0.12.11"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -438,27 +431,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
node_interop:
dependency: transitive
description:
name: node_interop
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
node_io:
dependency: transitive
description:
name: node_io
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
name: package_config name: package_config
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.3" version: "2.0.2"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -466,20 +445,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.0"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "2.0.2"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -493,7 +465,7 @@ packages:
name: provider name: provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.3.3" version: "6.0.1"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@ -507,7 +479,7 @@ packages:
name: pubspec_parse name: pubspec_parse
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.8" version: "1.1.0"
quiver: quiver:
dependency: transitive dependency: transitive
description: description:
@ -515,27 +487,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.1" version: "3.0.1"
quiver_hashcode:
dependency: transitive
description:
name: quiver_hashcode
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
name: shelf name: shelf
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.7.9" version: "1.2.0"
shelf_web_socket: shelf_web_socket:
dependency: transitive dependency: transitive
description: description:
name: shelf_web_socket name: shelf_web_socket
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.4+1" version: "1.0.1"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -547,7 +512,14 @@ packages:
name: source_gen name: source_gen
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.9.10+3" version: "1.1.1"
source_helper:
dependency: transitive
description:
name: source_helper
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -596,14 +568,14 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.2" version: "0.4.3"
timing: timing:
dependency: transitive dependency: transitive
description: description:
name: timing name: timing
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.1+3" version: "1.0.0"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -617,14 +589,14 @@ packages:
name: uuid name: uuid
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.2" version: "3.0.5"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
@ -638,7 +610,7 @@ packages:
name: web_socket_channel name: web_socket_channel
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "2.1.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:

@ -3,28 +3,31 @@ description: A dashboard app sample
version: 1.0.0+1 version: 1.0.0+1
publish_to: none publish_to: none
environment: environment:
sdk: ">=2.6.0 <3.0.0" sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
cloud_firestore: ^0.13.0 cloud_firestore: ^2.5.0
cupertino_icons: ^0.1.2 cupertino_icons: ^1.0.0
firebase_auth: ^0.16.1 firebase_auth: ^3.1.0
firebase_core: ^0.4.3 firebase_core: ^1.7.0
google_sign_in: ^5.0.0 google_sign_in: ^5.0.0
json_annotation: ^3.0.0 json_annotation: ^4.1.0
provider: ^4.0.0 provider: ^6.0.0
uuid: ^2.0.0 uuid: ^3.0.0
# The published version of charts_flutter is not up to date with master:
# https://github.com/google/charts/issues/678
charts_flutter: charts_flutter:
git: git:
url: git://github.com/google/charts.git url: git://github.com/google/charts.git
path: charts_flutter path: charts_flutter
ref: 03c2aa67d12952e7f3cbba1d87646d96a9a7cdc4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^1.8.0 build_runner: ^2.1.0
json_serializable: ^3.3.0 json_serializable: ^5.0.0
grinder: ^0.8.4 grinder: ^0.9.0
flutter_lints: ^1.0.0 flutter_lints: ^1.0.0
flutter: flutter:
uses-material-design: true uses-material-design: true

@ -9,7 +9,7 @@ import 'package:web_dashboard/src/api/mock.dart';
void main() { void main() {
group('mock dashboard API', () { group('mock dashboard API', () {
DashboardApi api; late DashboardApi api;
setUp(() { setUp(() {
api = MockDashboardApi(); api = MockDashboardApi();
@ -24,9 +24,10 @@ void main() {
test('delete', () async { test('delete', () async {
await api.categories.insert(Category('Coffees Drank')); await api.categories.insert(Category('Coffees Drank'));
var category = await api.categories.insert(Category('Miles Ran')); var category = await api.categories.insert(Category('Miles Ran'));
var removed = await api.categories.delete(category.id); var removed = await api.categories.delete(category.id!);
expect(removed.name, 'Miles Ran'); expect(removed, isNotNull);
expect(removed!.name, 'Miles Ran');
var categories = await api.categories.list(); var categories = await api.categories.list();
expect(categories, hasLength(1)); expect(categories, hasLength(1));
@ -34,10 +35,11 @@ void main() {
test('update', () async { test('update', () async {
var category = await api.categories.insert(Category('Coffees Drank')); var category = await api.categories.insert(Category('Coffees Drank'));
await api.categories.update(Category('Bagels Consumed'), category.id); await api.categories.update(Category('Bagels Consumed'), category.id!);
var latest = await api.categories.get(category.id); var latest = await api.categories.get(category.id!);
expect(latest.name, equals('Bagels Consumed')); expect(latest, isNotNull);
expect(latest!.name, equals('Bagels Consumed'));
}); });
test('subscribe', () async { test('subscribe', () async {
var stream = api.categories.subscribe(); var stream = api.categories.subscribe();
@ -51,7 +53,7 @@ void main() {
}); });
group('entry service', () { group('entry service', () {
Category category; late Category category;
DateTime dateTime = DateTime(2020, 1, 1, 30, 45); DateTime dateTime = DateTime(2020, 1, 1, 30, 45);
setUp(() async { setUp(() async {
@ -60,38 +62,38 @@ void main() {
}); });
test('insert', () async { test('insert', () async {
var entry = await api.entries.insert(category.id, Entry(1, dateTime)); var entry = await api.entries.insert(category.id!, Entry(1, dateTime));
expect(entry.value, 1); expect(entry.value, 1);
expect(entry.time, dateTime); expect(entry.time, dateTime);
}); });
test('delete', () async { test('delete', () async {
await api.entries.insert(category.id, Entry(1, dateTime)); await api.entries.insert(category.id!, Entry(1, dateTime));
var entry2 = await api.entries.insert(category.id, Entry(2, dateTime)); var entry2 = await api.entries.insert(category.id!, Entry(2, dateTime));
await api.entries.delete(category.id, entry2.id); await api.entries.delete(category.id!, entry2.id!);
var entries = await api.entries.list(category.id); var entries = await api.entries.list(category.id!);
expect(entries, hasLength(1)); expect(entries, hasLength(1));
}); });
test('update', () async { test('update', () async {
var entry = await api.entries.insert(category.id, Entry(1, dateTime)); var entry = await api.entries.insert(category.id!, Entry(1, dateTime));
var updated = var updated = await api.entries
await api.entries.update(category.id, entry.id, Entry(2, dateTime)); .update(category.id!, entry.id!, Entry(2, dateTime));
expect(updated.value, 2); expect(updated.value, 2);
}); });
test('subscribe', () async { test('subscribe', () async {
var stream = api.entries.subscribe(category.id); var stream = api.entries.subscribe(category.id!);
stream.listen(expectAsync1((x) { stream.listen(expectAsync1((x) {
expect(x, hasLength(1)); expect(x, hasLength(1));
expect(x.first.value, equals(1)); expect(x.first.value, equals(1));
}, count: 1)); }, count: 1));
await api.entries.insert(category.id, Entry(1, dateTime)); await api.entries.insert(category.id!, Entry(1, dateTime));
}); });
}); });
}); });

Loading…
Cancel
Save