1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-10 14:58:34 +00:00

Migrate web dashboard to null safety (#928)

* 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
This commit is contained in:
John Ryan
2021-10-12 14:38:34 -07:00
committed by GitHub
parent 0061b0d70d
commit 4893a24625
20 changed files with 277 additions and 301 deletions

View File

@@ -15,9 +15,9 @@ abstract class DashboardApi {
/// Manipulates [Category] data.
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);
@@ -30,9 +30,9 @@ abstract class CategoryApi {
/// Manipulates [Entry] data.
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);
@@ -49,7 +49,7 @@ class Category {
String name;
@JsonKey(ignore: true)
String id;
String? id;
Category(this.name);
@@ -76,7 +76,7 @@ class Entry {
DateTime time;
@JsonKey(ignore: true)
String id;
String? id;
Entry(this.value, this.time);

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
// 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.
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:google_sign_in/google_sign_in.dart';
@@ -25,21 +25,21 @@ class FirebaseAuthService implements Auth {
}
Future<User> _signIn() async {
GoogleSignInAccount googleUser;
GoogleSignInAccount? googleUser;
if (await isSignedIn) {
googleUser = await _googleSignIn.signInSilently();
} else {
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);
var authResult = await _auth.signInWithCredential(credential);
return _FirebaseUser(authResult.user.uid);
return _FirebaseUser(authResult.user!.uid);
}
@override

View File

@@ -2,8 +2,6 @@
// 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.
import 'dart:math';
import 'auth.dart';
class MockAuthService implements Auth {
@@ -12,11 +10,6 @@ class MockAuthService implements Auth {
@override
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();
}

View File

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

View File

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

View File

@@ -2,6 +2,8 @@
// 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.
import 'dart:async';
import 'package:flutter/material.dart';
import '../widgets/dialogs.dart';
@@ -13,8 +15,8 @@ class HomePage extends StatefulWidget {
final VoidCallback onSignOut;
const HomePage({
@required this.onSignOut,
Key key,
required this.onSignOut,
Key? key,
}) : super(key: key);
@override
@@ -86,7 +88,7 @@ class _HomePageState extends State<HomePage> {
}
Future<void> _handleSignOut() async {
var shouldSignOut = await showDialog<bool>(
var shouldSignOut = await (showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
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;
}

View File

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

View File

@@ -15,14 +15,14 @@ class EntryTotal {
/// Returns a list of [EntryTotal] objects. Each [EntryTotal] is the sum of
/// the values of all the entries on a given day.
List<EntryTotal> entryTotalsByDay(List<Entry> entries, int daysAgo,
{DateTime today}) {
List<EntryTotal> entryTotalsByDay(List<Entry>? entries, int daysAgo,
{DateTime? today}) {
today ??= DateTime.now();
return _entryTotalsByDay(entries, daysAgo, today).toList();
}
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 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
/// inner list is the group of entries on that day.
List<List<Entry>> _entriesInRange(
DateTime start, DateTime end, List<Entry> entries) =>
DateTime start, DateTime end, List<Entry>? entries) =>
_entriesInRangeImpl(start, end, entries).toList();
Iterable<List<Entry>> _entriesInRangeImpl(
DateTime start, DateTime end, List<Entry> entries) sync* {
DateTime start, DateTime end, List<Entry>? entries) sync* {
start = start.atMidnight;
end = end.atMidnight;
var d = start;
while (d.compareTo(end) <= 0) {
var es = <Entry>[];
for (var entry in entries) {
for (var entry in entries!) {
if (d.isSameDay(entry.time.atMidnight)) {
es.add(entry);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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