migrate place_tracker to go_router (#1529)

pull/1535/head
Miguel Beltran 2 years ago committed by GitHub
parent f66188c862
commit db94e92a26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -42,6 +42,27 @@ class Place {
starRating: starRating ?? this.starRating, starRating: starRating ?? this.starRating,
); );
} }
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Place &&
runtimeType == other.runtimeType &&
id == other.id &&
latLng == other.latLng &&
name == other.name &&
category == other.category &&
description == other.description &&
starRating == other.starRating;
@override
int get hashCode =>
id.hashCode ^
latLng.hashCode ^
name.hashCode ^
category.hashCode ^
description.hashCode ^
starRating.hashCode;
} }
enum PlaceCategory { enum PlaceCategory {

@ -4,17 +4,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:provider/provider.dart';
import 'place.dart'; import 'place.dart';
import 'place_tracker_app.dart';
import 'stub_data.dart'; import 'stub_data.dart';
class PlaceDetails extends StatefulWidget { class PlaceDetails extends StatefulWidget {
final Place place; final Place place;
final ValueChanged<Place> onChanged;
const PlaceDetails({ const PlaceDetails({
required this.place, required this.place,
required this.onChanged,
super.key, super.key,
}); });
@ -41,7 +41,7 @@ class _PlaceDetailsState extends State<PlaceDetails> {
child: IconButton( child: IconButton(
icon: const Icon(Icons.save, size: 30.0), icon: const Icon(Icons.save, size: 30.0),
onPressed: () { onPressed: () {
widget.onChanged(_place); _onChanged(_place);
Navigator.pop(context); Navigator.pop(context);
}, },
), ),
@ -61,7 +61,7 @@ class _PlaceDetailsState extends State<PlaceDetails> {
void initState() { void initState() {
_place = widget.place; _place = widget.place;
_nameController.text = _place.name; _nameController.text = _place.name;
_descriptionController.text = _place.description!; _descriptionController.text = _place.description ?? '';
return super.initState(); return super.initState();
} }
@ -113,12 +113,22 @@ class _PlaceDetailsState extends State<PlaceDetails> {
)); ));
}); });
} }
void _onChanged(Place value) {
// Replace the place with the modified version.
final newPlaces = List<Place>.from(context.read<AppState>().places);
final index = newPlaces.indexWhere((place) => place.id == value.id);
newPlaces[index] = value;
context.read<AppState>().setPlaces(newPlaces);
}
} }
class _DescriptionTextField extends StatelessWidget { class _DescriptionTextField extends StatelessWidget {
final TextEditingController controller; final TextEditingController controller;
final ValueChanged<String> onChanged; final ValueChanged<String> onChanged;
const _DescriptionTextField({ const _DescriptionTextField({
required this.controller, required this.controller,
required this.onChanged, required this.onChanged,
@ -151,6 +161,7 @@ class _Map extends StatelessWidget {
final GoogleMapController? mapController; final GoogleMapController? mapController;
final ArgumentCallback<GoogleMapController> onMapCreated; final ArgumentCallback<GoogleMapController> onMapCreated;
final Set<Marker> markers; final Set<Marker> markers;
const _Map({ const _Map({
required this.center, required this.center,
required this.mapController, required this.mapController,
@ -187,6 +198,7 @@ class _NameTextField extends StatelessWidget {
final TextEditingController controller; final TextEditingController controller;
final ValueChanged<String> onChanged; final ValueChanged<String> onChanged;
const _NameTextField({ const _NameTextField({
required this.controller, required this.controller,
required this.onChanged, required this.onChanged,
@ -304,6 +316,7 @@ class _StarBar extends StatelessWidget {
final int rating; final int rating;
final ValueChanged<int> onChanged; final ValueChanged<int> onChanged;
const _StarBar({ const _StarBar({
required this.rating, required this.rating,
required this.onChanged, required this.onChanged,

@ -3,10 +3,10 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'place.dart'; import 'place.dart';
import 'place_details.dart';
import 'place_tracker_app.dart'; import 'place_tracker_app.dart';
class PlaceList extends StatefulWidget { class PlaceList extends StatefulWidget {
@ -35,10 +35,7 @@ class _PlaceListState extends State<PlaceList> {
shrinkWrap: true, shrinkWrap: true,
children: state.places children: state.places
.where((place) => place.category == state.selectedCategory) .where((place) => place.category == state.selectedCategory)
.map((place) => _PlaceListTile( .map((place) => _PlaceListTile(place: place))
place: place,
onPlaceChanged: (value) => _onPlaceChanged(value),
))
.toList(), .toList(),
), ),
), ),
@ -51,16 +48,6 @@ class _PlaceListState extends State<PlaceList> {
Provider.of<AppState>(context, listen: false) Provider.of<AppState>(context, listen: false)
.setSelectedCategory(newCategory); .setSelectedCategory(newCategory);
} }
void _onPlaceChanged(Place value) {
// Replace the place with the modified version.
final newPlaces =
List<Place>.from(Provider.of<AppState>(context, listen: false).places);
final index = newPlaces.indexWhere((place) => place.id == value.id);
newPlaces[index] = value;
Provider.of<AppState>(context, listen: false).setPlaces(newPlaces);
}
} }
class _CategoryButton extends StatelessWidget { class _CategoryButton extends StatelessWidget {
@ -68,6 +55,7 @@ class _CategoryButton extends StatelessWidget {
final bool selected; final bool selected;
final ValueChanged<PlaceCategory> onCategoryChanged; final ValueChanged<PlaceCategory> onCategoryChanged;
const _CategoryButton({ const _CategoryButton({
required this.category, required this.category,
required this.selected, required this.selected,
@ -118,6 +106,7 @@ class _ListCategoryButtonBar extends StatelessWidget {
final PlaceCategory selectedCategory; final PlaceCategory selectedCategory;
final ValueChanged<PlaceCategory> onCategoryChanged; final ValueChanged<PlaceCategory> onCategoryChanged;
const _ListCategoryButtonBar({ const _ListCategoryButtonBar({
required this.selectedCategory, required this.selectedCategory,
required this.onCategoryChanged, required this.onCategoryChanged,
@ -151,24 +140,14 @@ class _ListCategoryButtonBar extends StatelessWidget {
class _PlaceListTile extends StatelessWidget { class _PlaceListTile extends StatelessWidget {
final Place place; final Place place;
final ValueChanged<Place> onPlaceChanged;
const _PlaceListTile({ const _PlaceListTile({
required this.place, required this.place,
required this.onPlaceChanged,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InkWell( return InkWell(
onTap: () => Navigator.push<void>( onTap: () => context.go('/place/${place.id}'),
context,
MaterialPageRoute(builder: (context) {
return PlaceDetails(
place: place,
onChanged: (value) => onPlaceChanged(value),
);
}),
),
child: Container( child: Container(
padding: const EdgeInsets.only(top: 16.0), padding: const EdgeInsets.only(top: 16.0),
child: Column( child: Column(

@ -5,13 +5,14 @@
import 'dart:async'; import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'place.dart'; import 'place.dart';
import 'place_details.dart';
import 'place_tracker_app.dart'; import 'place_tracker_app.dart';
class MapConfiguration { class MapConfiguration {
@ -77,11 +78,22 @@ class _PlaceMapState extends State<PlaceMap> {
MapConfiguration? _configuration; MapConfiguration? _configuration;
@override
void initState() {
super.initState();
context.read<AppState>().addListener(_watchMapConfigurationChanges);
}
@override
void dispose() {
context.read<AppState>().removeListener(_watchMapConfigurationChanges);
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_maybeUpdateMapConfiguration(); _watchMapConfigurationChanges();
var state = Provider.of<AppState>(context); var state = Provider.of<AppState>(context);
return Builder(builder: (context) { return Builder(builder: (context) {
// We need this additional builder here so that we can pass its context to // We need this additional builder here so that we can pass its context to
// _AddPlaceButtonBar's onSavePressed callback. This callback shows a // _AddPlaceButtonBar's onSavePressed callback. This callback shows a
@ -189,7 +201,7 @@ class _PlaceMapState extends State<PlaceMap> {
infoWindowParam: InfoWindow( infoWindowParam: InfoWindow(
title: 'New Place', title: 'New Place',
snippet: null, snippet: null,
onTap: () => _pushPlaceDetailsScreen(newPlace), onTap: () => context.go('/place/${newPlace.id}'),
), ),
draggableParam: false, draggableParam: false,
); );
@ -212,7 +224,7 @@ class _PlaceMapState extends State<PlaceMap> {
action: SnackBarAction( action: SnackBarAction(
label: 'Edit', label: 'Edit',
onPressed: () async { onPressed: () async {
_pushPlaceDetailsScreen(newPlace); context.go('/place/${newPlace.id}');
}, },
), ),
), ),
@ -221,14 +233,6 @@ class _PlaceMapState extends State<PlaceMap> {
// Add the new place to the places stored in appState. // Add the new place to the places stored in appState.
final newPlaces = List<Place>.from(appState.places)..add(newPlace); final newPlaces = List<Place>.from(appState.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.selectedCategory,
);
appState.setPlaces(newPlaces); appState.setPlaces(newPlaces);
} }
} }
@ -240,7 +244,7 @@ class _PlaceMapState extends State<PlaceMap> {
infoWindow: InfoWindow( infoWindow: InfoWindow(
title: place.name, title: place.name,
snippet: '${place.starRating} Star Rating', snippet: '${place.starRating} Star Rating',
onTap: () => _pushPlaceDetailsScreen(place), onTap: () => context.go('/place/${place.id}'),
), ),
icon: await _getPlaceMarkerIcon(context, place.category), icon: await _getPlaceMarkerIcon(context, place.category),
visible: place.category == visible: place.category ==
@ -250,11 +254,10 @@ class _PlaceMapState extends State<PlaceMap> {
return marker; return marker;
} }
Future<void> _maybeUpdateMapConfiguration() async { Future<void> _watchMapConfigurationChanges() async {
_configuration ??= final appState = context.read<AppState>();
MapConfiguration.of(Provider.of<AppState>(context, listen: false)); _configuration ??= MapConfiguration.of(appState);
final newConfiguration = final newConfiguration = MapConfiguration.of(appState);
MapConfiguration.of(Provider.of<AppState>(context, listen: false));
// Since we manually update [_configuration] when place or selectedCategory // Since we manually update [_configuration] when place or selectedCategory
// changes come from the [place_map], we should only enter this if statement // changes come from the [place_map], we should only enter this if statement
@ -270,9 +273,14 @@ class _PlaceMapState extends State<PlaceMap> {
} else { } else {
// At this point, we know the places have been updated from the list // At this point, we know the places have been updated from the list
// view. We need to reconfigure the map to respect the updates. // view. We need to reconfigure the map to respect the updates.
newConfiguration.places for (final place in newConfiguration.places) {
.where((p) => !_configuration!.places.contains(p)) final oldPlace =
.map((value) => _updateExistingPlaceMarker(place: value)); _configuration!.places.firstWhereOrNull((p) => p.id == place.id);
if (oldPlace == null || oldPlace != place) {
// New place or updated place.
_updateExistingPlaceMarker(place: place);
}
}
await _zoomToFitPlaces( await _zoomToFitPlaces(
_getPlacesForCategory( _getPlacesForCategory(
@ -299,27 +307,6 @@ class _PlaceMapState extends State<PlaceMap> {
}); });
} }
void _onPlaceChanged(Place value) {
// Replace the place with the modified version.
final newPlaces =
List<Place>.from(Provider.of<AppState>(context, listen: false).places);
final index = newPlaces.indexWhere((place) => place.id == value.id);
newPlaces[index] = value;
_updateExistingPlaceMarker(place: value);
// 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:
Provider.of<AppState>(context, listen: false).selectedCategory,
);
Provider.of<AppState>(context, listen: false).setPlaces(newPlaces);
}
void _onToggleMapTypePressed() { void _onToggleMapTypePressed() {
final nextType = final nextType =
MapType.values[(_currentMapType.index + 1) % MapType.values.length]; MapType.values[(_currentMapType.index + 1) % MapType.values.length];
@ -329,18 +316,6 @@ class _PlaceMapState extends State<PlaceMap> {
}); });
} }
void _pushPlaceDetailsScreen(Place place) {
Navigator.push<void>(
context,
MaterialPageRoute(builder: (context) {
return PlaceDetails(
place: place,
onChanged: (value) => _onPlaceChanged(value),
);
}),
);
}
Future<void> _showPlacesForSelectedCategory(PlaceCategory category) async { Future<void> _showPlacesForSelectedCategory(PlaceCategory category) async {
setState(() { setState(() {
for (var marker in List.of(_markedPlaces.keys)) { for (var marker in List.of(_markedPlaces.keys)) {
@ -412,15 +387,17 @@ class _PlaceMapState extends State<PlaceMap> {
maxLong = max(maxLong, place.longitude); maxLong = max(maxLong, place.longitude);
} }
await controller.animateCamera( WidgetsBinding.instance.addPostFrameCallback((_) {
CameraUpdate.newLatLngBounds( controller.animateCamera(
LatLngBounds( CameraUpdate.newLatLngBounds(
southwest: LatLng(minLat, minLong), LatLngBounds(
northeast: LatLng(maxLat, maxLong), southwest: LatLng(minLat, minLong),
northeast: LatLng(maxLat, maxLong),
),
48.0,
), ),
48.0, );
), });
);
} }
static Future<BitmapDescriptor> _getPlaceMarkerIcon( static Future<BitmapDescriptor> _getPlaceMarkerIcon(

@ -3,10 +3,12 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'place.dart'; import 'place.dart';
import 'place_details.dart';
import 'place_list.dart'; import 'place_list.dart';
import 'place_map.dart'; import 'place_map.dart';
import 'stub_data.dart'; import 'stub_data.dart';
@ -21,8 +23,26 @@ class PlaceTrackerApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const MaterialApp( return MaterialApp.router(
home: _PlaceTrackerHomePage(), routerConfig: GoRouter(routes: [
GoRoute(
path: '/',
builder: (context, state) => const _PlaceTrackerHomePage(),
routes: [
GoRoute(
path: 'place/:id',
builder: (context, state) {
final id = state.params['id']!;
final place = context
.read<AppState>()
.places
.singleWhere((place) => place.id == id);
return PlaceDetails(place: place);
},
),
],
),
]),
); );
} }
} }

@ -15,6 +15,8 @@ dependencies:
google_maps_flutter_web: ">=0.3.0+1 <0.5.0" google_maps_flutter_web: ">=0.3.0+1 <0.5.0"
provider: ^6.0.2 provider: ^6.0.2
uuid: ^3.0.4 uuid: ^3.0.4
go_router: ^5.2.4
collection: ^1.16.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

Loading…
Cancel
Save