diff --git a/place_tracker/lib/app_model.dart b/place_tracker/lib/app_model.dart deleted file mode 100644 index a23a5b6b1..000000000 --- a/place_tracker/lib/app_model.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter/material.dart'; - -class _AppModelScope extends InheritedWidget { - const _AppModelScope({ - Key key, - this.appModelState, - Widget child, - }) : super(key: key, child: child); - - final _AppModelState appModelState; - - @override - bool updateShouldNotify(_AppModelScope oldWidget) => true; -} - -class AppModel extends StatefulWidget { - AppModel({ - Key key, - @required this.initialState, - this.child, - }) : assert(initialState != null), - super(key: key); - - final T initialState; - final Widget child; - - @override - _AppModelState createState() => _AppModelState(); - - static T of(BuildContext context) { - final scope = - context.dependOnInheritedWidgetOfExactType<_AppModelScope>(); - return scope.appModelState.currentState; - } - - static void update(BuildContext context, T newState) { - final scope = - context.dependOnInheritedWidgetOfExactType<_AppModelScope>(); - scope.appModelState.updateState(newState); - } -} - -class _AppModelState extends State> { - @override - void initState() { - super.initState(); - currentState = widget.initialState; - } - - T currentState; - - void updateState(T newState) { - if (newState != currentState) { - setState(() { - currentState = newState; - }); - } - } - - @override - Widget build(BuildContext context) { - return _AppModelScope( - appModelState: this, - child: widget.child, - ); - } -} diff --git a/place_tracker/lib/main.dart b/place_tracker/lib/main.dart index 197539606..9d61258e4 100644 --- a/place_tracker/lib/main.dart +++ b/place_tracker/lib/main.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'place_tracker_app.dart'; void main() { - runApp(PlaceTrackerApp()); + runApp(ChangeNotifierProvider( + create: (context) => AppState(), + child: PlaceTrackerApp(), + )); } diff --git a/place_tracker/lib/place.dart b/place_tracker/lib/place.dart index 9391fe96e..619a989c7 100644 --- a/place_tracker/lib/place.dart +++ b/place_tracker/lib/place.dart @@ -29,6 +29,7 @@ class Place { final int starRating; double get latitude => latLng.latitude; + double get longitude => latLng.longitude; Place copyWith({ diff --git a/place_tracker/lib/place_list.dart b/place_tracker/lib/place_list.dart index ad4f243b7..dd76332ee 100644 --- a/place_tracker/lib/place_list.dart +++ b/place_tracker/lib/place_list.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'place.dart'; import 'place_details.dart'; @@ -16,24 +17,27 @@ class PlaceListState extends State { void _onCategoryChanged(PlaceCategory newCategory) { _scrollController.jumpTo(0.0); - AppState.updateWith(context, selectedCategory: newCategory); + Provider.of(context, listen: false) + .setSelectedCategory(newCategory); } void _onPlaceChanged(Place value) { // Replace the place with the modified version. - final newPlaces = List.from(AppState.of(context).places); + final newPlaces = + List.from(Provider.of(context, listen: false).places); final index = newPlaces.indexWhere((place) => place.id == value.id); newPlaces[index] = value; - AppState.updateWith(context, places: newPlaces); + Provider.of(context, listen: false).setPlaces(newPlaces); } @override Widget build(BuildContext context) { + var state = Provider.of(context); return Column( children: [ _ListCategoryButtonBar( - selectedCategory: AppState.of(context).selectedCategory, + selectedCategory: state.selectedCategory, onCategoryChanged: (value) => _onCategoryChanged(value), ), Expanded( @@ -41,10 +45,8 @@ class PlaceListState extends State { padding: const EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 8.0), controller: _scrollController, shrinkWrap: true, - children: AppState.of(context) - .places - .where((place) => - place.category == AppState.of(context).selectedCategory) + children: state.places + .where((place) => place.category == state.selectedCategory) .map((place) => _PlaceListTile( place: place, onPlaceChanged: (value) => _onPlaceChanged(value), diff --git a/place_tracker/lib/place_map.dart b/place_tracker/lib/place_map.dart index c03364fa9..c266f8386 100644 --- a/place_tracker/lib/place_map.dart +++ b/place_tracker/lib/place_map.dart @@ -1,8 +1,10 @@ import 'dart:async'; import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:provider/provider.dart'; import 'package:uuid/uuid.dart'; import 'place.dart'; @@ -65,7 +67,7 @@ class PlaceMapState extends State { // Draw initial place markers on creation so that we have something // interesting to look at. var markers = {}; - for (var place in AppState.of(context).places) { + for (var place in Provider.of(context, listen: false).places) { markers.add(await _createPlaceMarker(context, place)); } setState(() { @@ -75,7 +77,7 @@ class PlaceMapState extends State { // Zoom to fit the initially selected category. await _zoomToFitPlaces( _getPlacesForCategory( - AppState.of(context).selectedCategory, + Provider.of(context, listen: false).selectedCategory, _markedPlaces.values.toList(), ), ); @@ -91,7 +93,8 @@ class PlaceMapState extends State { onTap: () => _pushPlaceDetailsScreen(place), ), icon: await _getPlaceMarkerIcon(context, place.category), - visible: place.category == AppState.of(context).selectedCategory, + visible: place.category == + Provider.of(context, listen: false).selectedCategory, ); _markedPlaces[marker] = place; return marker; @@ -113,7 +116,8 @@ class PlaceMapState extends State { void _onPlaceChanged(Place value) { // Replace the place with the modified version. - final newPlaces = List.from(AppState.of(context).places); + final newPlaces = + List.from(Provider.of(context, listen: false).places); final index = newPlaces.indexWhere((place) => place.id == value.id); newPlaces[index] = value; @@ -124,10 +128,11 @@ class PlaceMapState extends State { // in the main build method due to a modified AppState. _configuration = MapConfiguration( places: newPlaces, - selectedCategory: AppState.of(context).selectedCategory, + selectedCategory: + Provider.of(context, listen: false).selectedCategory, ); - AppState.updateWith(context, places: newPlaces); + Provider.of(context, listen: false).setPlaces(newPlaces); } void _updateExistingPlaceMarker({@required Place place}) { @@ -159,7 +164,7 @@ class PlaceMapState extends State { } Future _switchSelectedCategory(PlaceCategory category) async { - AppState.updateWith(context, selectedCategory: category); + Provider.of(context, listen: false).setSelectedCategory(category); await _showPlacesForSelectedCategory(category); } @@ -233,11 +238,12 @@ class PlaceMapState extends State { id: Uuid().v1(), latLng: _pendingMarker.position, name: _pendingMarker.infoWindow.title, - category: AppState.of(context).selectedCategory, + category: + Provider.of(context, listen: false).selectedCategory, ); - var placeMarker = await _getPlaceMarkerIcon( - context, AppState.of(context).selectedCategory); + var placeMarker = await _getPlaceMarkerIcon(context, + Provider.of(context, listen: false).selectedCategory); setState(() { final updatedMarker = _pendingMarker.copyWith( @@ -275,18 +281,20 @@ class PlaceMapState extends State { ); // Add the new place to the places stored in appState. - final newPlaces = List.from(AppState.of(context).places) - ..add(newPlace); + final newPlaces = + List.from(Provider.of(context, listen: false).places) + ..add(newPlace); // Manually update our map configuration here since our map is already // updated with the new marker. Otherwise, the map would be reconfigured // in the main build method due to a modified AppState. _configuration = MapConfiguration( places: newPlaces, - selectedCategory: AppState.of(context).selectedCategory, + selectedCategory: + Provider.of(context, listen: false).selectedCategory, ); - AppState.updateWith(context, places: newPlaces); + Provider.of(context, listen: false).setPlaces(newPlaces); } } @@ -309,8 +317,10 @@ class PlaceMapState extends State { } Future _maybeUpdateMapConfiguration() async { - _configuration ??= MapConfiguration.of(AppState.of(context)); - final newConfiguration = MapConfiguration.of(AppState.of(context)); + _configuration ??= + MapConfiguration.of(Provider.of(context, listen: false)); + final newConfiguration = + MapConfiguration.of(Provider.of(context, listen: false)); // Since we manually update [_configuration] when place or selectedCategory // changes come from the [place_map], we should only enter this if statement @@ -344,6 +354,7 @@ class PlaceMapState extends State { @override Widget build(BuildContext context) { _maybeUpdateMapConfiguration(); + var state = Provider.of(context); return Builder(builder: (context) { // We need this additional builder here so that we can pass its context to @@ -364,7 +375,7 @@ class PlaceMapState extends State { onCameraMove: (position) => _lastMapPosition = position.target, ), _CategoryButtonBar( - selectedPlaceCategory: AppState.of(context).selectedCategory, + selectedPlaceCategory: state.selectedCategory, visible: _pendingMarker == null, onChanged: _switchSelectedCategory, ), diff --git a/place_tracker/lib/place_tracker_app.dart b/place_tracker/lib/place_tracker_app.dart index dde643242..d5b38c660 100644 --- a/place_tracker/lib/place_tracker_app.dart +++ b/place_tracker/lib/place_tracker_app.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:provider/provider.dart'; -import 'app_model.dart'; import 'place.dart'; import 'place_list.dart'; import 'place_map.dart'; @@ -12,23 +12,10 @@ enum PlaceTrackerViewType { list, } -class PlaceTrackerApp extends StatefulWidget { - @override - _PlaceTrackerAppState createState() => _PlaceTrackerAppState(); -} - -class _PlaceTrackerAppState extends State { - AppState appState = AppState(); - +class PlaceTrackerApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - builder: (context, child) { - return AppModel( - initialState: AppState(), - child: child, - ); - }, home: _PlaceTrackerHomePage(), ); } @@ -39,6 +26,7 @@ class _PlaceTrackerHomePage extends StatelessWidget { @override Widget build(BuildContext context) { + var state = Provider.of(context); return Scaffold( appBar: AppBar( title: Row( @@ -57,18 +45,16 @@ class _PlaceTrackerHomePage extends StatelessWidget { padding: EdgeInsets.fromLTRB(0.0, 0.0, 16.0, 0.0), child: IconButton( icon: Icon( - AppState.of(context).viewType == PlaceTrackerViewType.map + state.viewType == PlaceTrackerViewType.map ? Icons.list : Icons.map, size: 32.0, ), onPressed: () { - AppState.updateWith( - context, - viewType: - AppState.of(context).viewType == PlaceTrackerViewType.map - ? PlaceTrackerViewType.list - : PlaceTrackerViewType.map, + state.setViewType( + state.viewType == PlaceTrackerViewType.map + ? PlaceTrackerViewType.list + : PlaceTrackerViewType.map, ); }, ), @@ -76,61 +62,41 @@ class _PlaceTrackerHomePage extends StatelessWidget { ], ), body: IndexedStack( - index: - AppState.of(context).viewType == PlaceTrackerViewType.map ? 0 : 1, + index: state.viewType == PlaceTrackerViewType.map ? 0 : 1, children: [ PlaceMap(center: const LatLng(45.521563, -122.677433)), - PlaceList(), + PlaceList() ], ), ); } } -class AppState { - const AppState({ +class AppState extends ChangeNotifier { + AppState({ this.places = StubData.places, this.selectedCategory = PlaceCategory.favorite, this.viewType = PlaceTrackerViewType.map, }) : assert(places != null), assert(selectedCategory != null); - final List places; - final PlaceCategory selectedCategory; - final PlaceTrackerViewType viewType; + List places; + PlaceCategory selectedCategory; + PlaceTrackerViewType viewType; - AppState copyWith({ - List places, - PlaceCategory selectedCategory, - PlaceTrackerViewType viewType, - }) { - return AppState( - places: places ?? this.places, - selectedCategory: selectedCategory ?? this.selectedCategory, - viewType: viewType ?? this.viewType, - ); + void setViewType(PlaceTrackerViewType viewType) { + this.viewType = viewType; + notifyListeners(); } - static AppState of(BuildContext context) => AppModel.of(context); - - static void update(BuildContext context, AppState newState) { - AppModel.update(context, newState); + void setSelectedCategory(PlaceCategory newCategory) { + selectedCategory = newCategory; + notifyListeners(); } - static void updateWith( - BuildContext context, { - List places, - PlaceCategory selectedCategory, - PlaceTrackerViewType viewType, - }) { - update( - context, - AppState.of(context).copyWith( - places: places, - selectedCategory: selectedCategory, - viewType: viewType, - ), - ); + void setPlaces(List newPlaces) { + places = newPlaces; + notifyListeners(); } @override diff --git a/place_tracker/pubspec.lock b/place_tracker/pubspec.lock index bfb5656f8..ef027ab5b 100644 --- a/place_tracker/pubspec.lock +++ b/place_tracker/pubspec.lock @@ -116,6 +116,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.8" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4" path: dependency: transitive description: @@ -144,6 +151,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + provider: + dependency: "direct dev" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.5+1" quiver: dependency: transitive description: diff --git a/place_tracker/pubspec.yaml b/place_tracker/pubspec.yaml index 2fbd29970..79db48493 100644 --- a/place_tracker/pubspec.yaml +++ b/place_tracker/pubspec.yaml @@ -18,6 +18,7 @@ dev_dependencies: flutter_test: sdk: flutter pedantic: ^1.9.0 + provider: ^4.0.5+1 flutter: assets: