Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State Restoration support for Veggie Seasons app #433

Merged
merged 5 commits into from
Nov 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
File renamed without changes.
42 changes: 42 additions & 0 deletions experimental/veggieseasons/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Veggie Seasons

An iOS app that shows which fruits and vegetables are currently in season. It
showcases Flutter's Cupertino package.

[Available now in the App Store!](https://itunes.apple.com/is/app/veggie-seasons/id1450855435)

**NOTE:** While Flutter supports many platforms, this application is designed
specifically for iOS. It's not intended to be run on Android, web, or desktop.

## Goals

* Show how to build an interface that iOS users will feel right at home
with.
* Show how Flutter's Cupertino widgets work together.

## The important bits

### `/screens/*`

These are the screens presented in the app, roughly analogous to
UIViewControllers. `HomeScreen` is the root, and the others are shown
as the user navigates.

### `/widgets/search_bar.dart`

An example of how to construct an Cupertino-style search bar. The
Flutter team [is working on an official widget](https://github.com/flutter/flutter/issues/9784)
for this. Once that effort is complete, developers will not need to roll
their own search bars, so to speak.

## Questions/issues

If you have a general question about any of the techniques you see in
the sample, the best places to go are:

* [The FlutterDev Google Group](https://groups.google.com/forum/#!forum/flutter-dev)
* [The Flutter Gitter channel](https://gitter.im/flutter/flutter)
* [StackOverflow](https://stackoverflow.com/questions/tagged/flutter)

If you run into an issue with the sample itself, please file an issue
in the [main Flutter repo](https://github.com/flutter/flutter/issues).
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@ class AppState extends ChangeNotifier {
notifyListeners();
}

/// Used in tests to set the season independent of the current date.
static Season debugCurrentSeason;

static Season _getSeasonForDate(DateTime date) {
if (debugCurrentSeason != null) {
return debugCurrentSeason;
}

// Technically the start and end dates of seasons can vary by a day or so,
// but this is close enough for produce.
switch (date.month) {
Expand Down
89 changes: 89 additions & 0 deletions experimental/veggieseasons/lib/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2018 The Flutter team. 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:flutter/cupertino.dart';
import 'package:flutter/services.dart' show DeviceOrientation, SystemChrome;
import 'package:provider/provider.dart';
import 'package:veggieseasons/data/app_state.dart';
import 'package:veggieseasons/data/preferences.dart';
import 'package:veggieseasons/screens/home.dart';

void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);

runApp(
RootRestorationScope(
restorationId: 'root',
child: VeggieApp(),
),
);
}

class VeggieApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _VeggieAppState();
}

class _VeggieAppState extends State<VeggieApp> with RestorationMixin {
final _RestorableAppState _appState = _RestorableAppState();

@override
String get restorationId => 'wrapper';

@override
void restoreState(RestorationBucket oldBucket, bool initialRestore) {
registerForRestoration(_appState, 'state');
}

@override
void dispose() {
_appState.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: _appState.value,
),
ChangeNotifierProvider(
create: (_) => Preferences()..load(),
),
],
child: CupertinoApp(
debugShowCheckedModeBanner: false,
home: HomeScreen(restorationId: 'home'),
restorationScopeId: 'app',
),
);
}
}

class _RestorableAppState extends RestorableListenable<AppState> {
@override
AppState createDefaultValue() {
return AppState();
}

@override
AppState fromPrimitives(Object data) {
final appState = AppState();
final favorites = (data as List<dynamic>).cast<int>();
for (var id in favorites) {
appState.setFavorite(id, true);
}
return appState;
}

@override
Object toPrimitives() {
return value.favoriteVeggies.map((veggie) => veggie.id).toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,15 +238,42 @@ class InfoView extends StatelessWidget {

class DetailsScreen extends StatefulWidget {
final int id;
final String restorationId;

DetailsScreen(this.id);
DetailsScreen({this.id, this.restorationId});

static String show(NavigatorState navigator, int veggieId) {
return navigator.restorablePush<void>(_routeBuilder, arguments: veggieId);
}

static Route<void> _routeBuilder(BuildContext context, Object arguments) {
final veggieId = arguments as int;
return CupertinoPageRoute(
builder: (context) => DetailsScreen(id: veggieId, restorationId: 'details'),
fullscreenDialog: true,
);
}

@override
_DetailsScreenState createState() => _DetailsScreenState();
}

class _DetailsScreenState extends State<DetailsScreen> {
int _selectedViewIndex = 0;
class _DetailsScreenState extends State<DetailsScreen> with RestorationMixin {
final RestorableInt _selectedViewIndex = RestorableInt(0);

@override
String get restorationId => widget.restorationId;

@override
void restoreState(RestorationBucket oldBucket, bool initialRestore) {
registerForRestoration(_selectedViewIndex, 'tab');
}

@override
void dispose() {
_selectedViewIndex.dispose();
super.dispose();
}

Widget _buildHeader(BuildContext context, AppState model) {
final veggie = model.getVeggie(widget.id);
Expand Down Expand Up @@ -282,33 +309,37 @@ class _DetailsScreenState extends State<DetailsScreen> {
Widget build(BuildContext context) {
final appState = Provider.of<AppState>(context);

return CupertinoPageScaffold(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: ListView(
children: [
_buildHeader(context, appState),
SizedBox(height: 20),
CupertinoSegmentedControl<int>(
children: {
0: Text('Facts & Info'),
1: Text('Trivia'),
},
groupValue: _selectedViewIndex,
onValueChanged: (value) {
setState(() => _selectedViewIndex = value);
},
),
_selectedViewIndex == 0
? InfoView(widget.id)
: TriviaView(widget.id),
],
return UnmanagedRestorationScope(
bucket: bucket,
child: CupertinoPageScaffold(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: ListView(
restorationId: 'list',
children: [
_buildHeader(context, appState),
SizedBox(height: 20),
CupertinoSegmentedControl<int>(
children: {
0: Text('Facts & Info'),
1: Text('Trivia'),
},
groupValue: _selectedViewIndex.value,
onValueChanged: (value) {
setState(() => _selectedViewIndex.value = value);
},
),
_selectedViewIndex.value == 0
? InfoView(widget.id)
: TriviaView(id: widget.id, restorationId: 'trivia'),
],
),
),
),
],
],
),
),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ import 'package:veggieseasons/styles.dart';
import 'package:veggieseasons/widgets/veggie_headline.dart';

class FavoritesScreen extends StatelessWidget {
FavoritesScreen({this.restorationId, Key key}) : super(key: key);

final String restorationId;

@override
Widget build(BuildContext context) {
return CupertinoTabView(
restorationScopeId: restorationId,
builder: (context) {
final model = Provider.of<AppState>(context);

Expand All @@ -32,6 +37,7 @@ class FavoritesScreen extends StatelessWidget {
),
)
: ListView(
restorationId: 'list',
children: [
SizedBox(height: 24),
for (Veggie veggie in model.favoriteVeggies)
Expand Down
55 changes: 55 additions & 0 deletions experimental/veggieseasons/lib/screens/home.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2018 The Flutter team. 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:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
import 'package:veggieseasons/screens/favorites.dart';
import 'package:veggieseasons/screens/list.dart';
import 'package:veggieseasons/screens/search.dart';
import 'package:veggieseasons/screens/settings.dart';

class HomeScreen extends StatelessWidget {
HomeScreen({Key key, this.restorationId}) : super(key: key);

final String restorationId;

@override
Widget build(BuildContext context) {
return RestorationScope(
restorationId: restorationId,
child: CupertinoTabScaffold(
restorationId: 'scaffold',
tabBar: CupertinoTabBar(items: [
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.book),
label: 'My Garden',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.search),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
label: 'Settings',
),
]),
tabBuilder: (context, index) {
if (index == 0) {
return ListScreen(restorationId: 'list');
} else if (index == 1) {
return FavoritesScreen(restorationId: 'favorites');
} else if (index == 2) {
return SearchScreen(restorationId: 'search');
} else {
return SettingsScreen(restorationId: 'settings');
}
},
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import 'package:veggieseasons/styles.dart';
import 'package:veggieseasons/widgets/veggie_card.dart';

class ListScreen extends StatelessWidget {
ListScreen({this.restorationId, Key key}) : super(key: key);

final String restorationId;

Widget _generateVeggieRow(Veggie veggie, Preferences prefs,
{bool inSeason = true}) {
return Padding(
Expand All @@ -29,6 +33,7 @@ class ListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return CupertinoTabView(
restorationScopeId: restorationId,
builder: (context) {
var dateString = DateFormat('MMMM y').format(DateTime.now());

Expand All @@ -38,6 +43,7 @@ class ListScreen extends StatelessWidget {
return SafeArea(
bottom: false,
child: ListView.builder(
restorationId: 'list',
itemCount: appState.allVeggies.length + 2,
itemBuilder: (context, index) {
if (index == 0) {
Expand Down
Loading