1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-08 13:58:47 +00:00

[place_tracker] ChangeNotifierProvider for state management (#424)

This commit is contained in:
Tushar Ojha
2020-06-13 05:20:46 +05:30
committed by GitHub
parent af5be70f34
commit 084c532ac0
8 changed files with 83 additions and 151 deletions

View File

@@ -1,67 +0,0 @@
import 'package:flutter/material.dart';
class _AppModelScope<T> extends InheritedWidget {
const _AppModelScope({
Key key,
this.appModelState,
Widget child,
}) : super(key: key, child: child);
final _AppModelState<T> appModelState;
@override
bool updateShouldNotify(_AppModelScope oldWidget) => true;
}
class AppModel<T> extends StatefulWidget {
AppModel({
Key key,
@required this.initialState,
this.child,
}) : assert(initialState != null),
super(key: key);
final T initialState;
final Widget child;
@override
_AppModelState<T> createState() => _AppModelState<T>();
static T of<T>(BuildContext context) {
final scope =
context.dependOnInheritedWidgetOfExactType<_AppModelScope<T>>();
return scope.appModelState.currentState;
}
static void update<T>(BuildContext context, T newState) {
final scope =
context.dependOnInheritedWidgetOfExactType<_AppModelScope<T>>();
scope.appModelState.updateState(newState);
}
}
class _AppModelState<T> extends State<AppModel<T>> {
@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<T>(
appModelState: this,
child: widget.child,
);
}
}

View File

@@ -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(),
));
}

View File

@@ -29,6 +29,7 @@ class Place {
final int starRating;
double get latitude => latLng.latitude;
double get longitude => latLng.longitude;
Place copyWith({

View File

@@ -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<PlaceList> {
void _onCategoryChanged(PlaceCategory newCategory) {
_scrollController.jumpTo(0.0);
AppState.updateWith(context, selectedCategory: newCategory);
Provider.of<AppState>(context, listen: false)
.setSelectedCategory(newCategory);
}
void _onPlaceChanged(Place value) {
// Replace the place with the modified version.
final newPlaces = List<Place>.from(AppState.of(context).places);
final newPlaces =
List<Place>.from(Provider.of<AppState>(context, listen: false).places);
final index = newPlaces.indexWhere((place) => place.id == value.id);
newPlaces[index] = value;
AppState.updateWith(context, places: newPlaces);
Provider.of<AppState>(context, listen: false).setPlaces(newPlaces);
}
@override
Widget build(BuildContext context) {
var state = Provider.of<AppState>(context);
return Column(
children: <Widget>[
_ListCategoryButtonBar(
selectedCategory: AppState.of(context).selectedCategory,
selectedCategory: state.selectedCategory,
onCategoryChanged: (value) => _onCategoryChanged(value),
),
Expanded(
@@ -41,10 +45,8 @@ class PlaceListState extends State<PlaceList> {
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),

View File

@@ -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<PlaceMap> {
// Draw initial place markers on creation so that we have something
// interesting to look at.
var markers = <Marker>{};
for (var place in AppState.of(context).places) {
for (var place in Provider.of<AppState>(context, listen: false).places) {
markers.add(await _createPlaceMarker(context, place));
}
setState(() {
@@ -75,7 +77,7 @@ class PlaceMapState extends State<PlaceMap> {
// Zoom to fit the initially selected category.
await _zoomToFitPlaces(
_getPlacesForCategory(
AppState.of(context).selectedCategory,
Provider.of<AppState>(context, listen: false).selectedCategory,
_markedPlaces.values.toList(),
),
);
@@ -91,7 +93,8 @@ class PlaceMapState extends State<PlaceMap> {
onTap: () => _pushPlaceDetailsScreen(place),
),
icon: await _getPlaceMarkerIcon(context, place.category),
visible: place.category == AppState.of(context).selectedCategory,
visible: place.category ==
Provider.of<AppState>(context, listen: false).selectedCategory,
);
_markedPlaces[marker] = place;
return marker;
@@ -113,7 +116,8 @@ class PlaceMapState extends State<PlaceMap> {
void _onPlaceChanged(Place value) {
// Replace the place with the modified version.
final newPlaces = List<Place>.from(AppState.of(context).places);
final newPlaces =
List<Place>.from(Provider.of<AppState>(context, listen: false).places);
final index = newPlaces.indexWhere((place) => place.id == value.id);
newPlaces[index] = value;
@@ -124,10 +128,11 @@ class PlaceMapState extends State<PlaceMap> {
// in the main build method due to a modified AppState.
_configuration = MapConfiguration(
places: newPlaces,
selectedCategory: AppState.of(context).selectedCategory,
selectedCategory:
Provider.of<AppState>(context, listen: false).selectedCategory,
);
AppState.updateWith(context, places: newPlaces);
Provider.of<AppState>(context, listen: false).setPlaces(newPlaces);
}
void _updateExistingPlaceMarker({@required Place place}) {
@@ -159,7 +164,7 @@ class PlaceMapState extends State<PlaceMap> {
}
Future<void> _switchSelectedCategory(PlaceCategory category) async {
AppState.updateWith(context, selectedCategory: category);
Provider.of<AppState>(context, listen: false).setSelectedCategory(category);
await _showPlacesForSelectedCategory(category);
}
@@ -233,11 +238,12 @@ class PlaceMapState extends State<PlaceMap> {
id: Uuid().v1(),
latLng: _pendingMarker.position,
name: _pendingMarker.infoWindow.title,
category: AppState.of(context).selectedCategory,
category:
Provider.of<AppState>(context, listen: false).selectedCategory,
);
var placeMarker = await _getPlaceMarkerIcon(
context, AppState.of(context).selectedCategory);
var placeMarker = await _getPlaceMarkerIcon(context,
Provider.of<AppState>(context, listen: false).selectedCategory);
setState(() {
final updatedMarker = _pendingMarker.copyWith(
@@ -275,18 +281,20 @@ class PlaceMapState extends State<PlaceMap> {
);
// Add the new place to the places stored in appState.
final newPlaces = List<Place>.from(AppState.of(context).places)
..add(newPlace);
final newPlaces =
List<Place>.from(Provider.of<AppState>(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<AppState>(context, listen: false).selectedCategory,
);
AppState.updateWith(context, places: newPlaces);
Provider.of<AppState>(context, listen: false).setPlaces(newPlaces);
}
}
@@ -309,8 +317,10 @@ class PlaceMapState extends State<PlaceMap> {
}
Future<void> _maybeUpdateMapConfiguration() async {
_configuration ??= MapConfiguration.of(AppState.of(context));
final newConfiguration = MapConfiguration.of(AppState.of(context));
_configuration ??=
MapConfiguration.of(Provider.of<AppState>(context, listen: false));
final newConfiguration =
MapConfiguration.of(Provider.of<AppState>(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<PlaceMap> {
@override
Widget build(BuildContext context) {
_maybeUpdateMapConfiguration();
var state = Provider.of<AppState>(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<PlaceMap> {
onCameraMove: (position) => _lastMapPosition = position.target,
),
_CategoryButtonBar(
selectedPlaceCategory: AppState.of(context).selectedCategory,
selectedPlaceCategory: state.selectedCategory,
visible: _pendingMarker == null,
onChanged: _switchSelectedCategory,
),

View File

@@ -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<PlaceTrackerApp> {
AppState appState = AppState();
class PlaceTrackerApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) {
return AppModel<AppState>(
initialState: AppState(),
child: child,
);
},
home: _PlaceTrackerHomePage(),
);
}
@@ -39,6 +26,7 @@ class _PlaceTrackerHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
var state = Provider.of<AppState>(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: <Widget>[
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<Place> places;
final PlaceCategory selectedCategory;
final PlaceTrackerViewType viewType;
List<Place> places;
PlaceCategory selectedCategory;
PlaceTrackerViewType viewType;
AppState copyWith({
List<Place> 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<AppState>(context);
static void update(BuildContext context, AppState newState) {
AppModel.update<AppState>(context, newState);
void setSelectedCategory(PlaceCategory newCategory) {
selectedCategory = newCategory;
notifyListeners();
}
static void updateWith(
BuildContext context, {
List<Place> places,
PlaceCategory selectedCategory,
PlaceTrackerViewType viewType,
}) {
update(
context,
AppState.of(context).copyWith(
places: places,
selectedCategory: selectedCategory,
viewType: viewType,
),
);
void setPlaces(List<Place> newPlaces) {
places = newPlaces;
notifyListeners();
}
@override