Skip to content

Commit 413e79b

Browse files
brianeganmjohnsullivan
authored andcommitted
App Maintenance and more Navigation recipes (cfug#995)
* WIP * WIP * Fix themes typo * New recipes and updates - pushNamed Navigation - Error Reporting - Update typo
1 parent 5f9b8f6 commit 413e79b

File tree

4 files changed

+371
-1
lines changed

4 files changed

+371
-1
lines changed

cookbook/design/themes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ the root of our apps by the `MaterialApp`!
1212

1313
After we define a Theme, we can use it within our own Widgets. In addition, the
1414
Material Widgets provided by Flutter will use our Theme to set the background
15-
colors and and font styles for AppBars, Buttons, Checkboxes, and more.
15+
colors and font styles for AppBars, Buttons, Checkboxes, and more.
1616

1717
## Creating an app theme
1818

cookbook/index.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ reference to help you build up an application.
4343
* [Navigate to a new screen and back](/cookbook/navigation/navigation-basics/)
4444
* [Send data to a new screen](/cookbook/navigation/passing-data/)
4545
* [Return data from a screen](/cookbook/navigation/returning-data/)
46+
* [Navigate with named routes](/cookbook/networking/named-routes/)
4647
* [Animating a Widget across screens](/cookbook/navigation/hero-animations/)
4748

4849
## Animation
@@ -68,3 +69,7 @@ reference to help you build up an application.
6869
* [Handling changes to a text field](/cookbook/forms/text-field-changes/)
6970
* [Focus a text field](/cookbook/forms/focus/)
7071
* [Building a form with validation](/cookbook/forms/validation/)
72+
73+
## App Maintenance
74+
75+
* [Report errors to a service](/cookbook/maintenance/error-reporting/)
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
---
2+
layout: page
3+
title: "Report errors to a service"
4+
permalink: /cookbook/maintenance/error-reporting/
5+
---
6+
7+
While we always do our best to create apps that are free of bugs, they're sure
8+
to crop up from time to time. Since buggy apps lead to unhappy
9+
users and customers, it's important to understand how often our users experience
10+
bugs and where those bugs occur. That way, we can prioritize the bugs with the
11+
highest impact and work to fix them.
12+
13+
How can we determine how often our users experiences bugs? Whenever an error
14+
occurs, we can create a report containing the error that occurred and the
15+
associated stacktrace. We can then send the report to an error tracking service,
16+
such as Sentry, Fabric, or Rollbar.
17+
18+
The error tracking service will then aggregate all of the crashes our users
19+
experience and group them together for us. This allows us to know how often our
20+
app fails and where our users run into trouble.
21+
22+
In this recipe, we'll see how to report errors to the
23+
[Sentry](https://sentry.io/welcome/) crash reporting service.
24+
25+
## Directions
26+
27+
1. Get a DSN from Sentry
28+
2. Import the Sentry package
29+
3. Create a `SentryClient`
30+
4. Create a function to report errors
31+
5. Catch and report Dart errors
32+
6. Catch and report Flutter errors
33+
34+
## 1. Get a DSN from Sentry
35+
36+
Before we can report errors to Sentry, we'll need a "DSN" to uniquely identify
37+
our app with the Sentry.io service.
38+
39+
To get a DSN, please:
40+
41+
1. [Create an account with Sentry](https://sentry.io/signup/)
42+
2. Log in to the account
43+
3. Create a new app
44+
4. Copy the DSN
45+
46+
## 2. Import the Sentry package
47+
48+
Next, we'll need to import the
49+
[`sentry`](https://pub.dartlang.org/packages/sentry) package into our app. The
50+
sentry package will make it easier for us to send error reports to the Sentry
51+
error tracking service.
52+
53+
```yaml
54+
dependencies:
55+
sentry: <latest_version>
56+
```
57+
58+
## 3. Create a `SentryClient`
59+
60+
We can now create a `SentryClient`. We will use the `SentryClient` to send
61+
error reports to the sentry service!
62+
63+
<!-- skip -->
64+
```dart
65+
final SentryClient _sentry = new SentryClient(dsn: "App DSN goes Here");
66+
```
67+
68+
## 4. Create a function to report errors
69+
70+
With Sentry all set up, we can begin to report errors! Since we don't want to
71+
report errors to Sentry during development, we'll first create a function that
72+
let's us know whether we're in debug or production mode.
73+
74+
<!-- skip -->
75+
```dart
76+
bool get isInDebugMode {
77+
// Assume we're in production mode
78+
bool inDebugMode = false;
79+
80+
// Assert expressions are only evaluated during development. They are ignored
81+
// in production. Therefore, this code will only turn `inDebugMode` to true
82+
// in our development environments!
83+
assert(inDebugMode = true);
84+
85+
return inDebugMode;
86+
}
87+
```
88+
89+
Next, we can use this function in combination with the `SentryClient` to report
90+
errors when our app is in production mode.
91+
92+
<!-- skip -->
93+
```dart
94+
Future<Null> _reportError(dynamic error, dynamic stackTrace) async {
95+
// Print the exception to the console
96+
print('Caught error: $error');
97+
if (isInDebugMode) {
98+
// Print the full stacktrace in debug mode
99+
print(stackTrace);
100+
return;
101+
} else {
102+
// Send the Exception and Stacktrace to Sentry in Production mode
103+
_sentry.captureException(
104+
exception: error,
105+
stackTrace: stackTrace,
106+
);
107+
}
108+
}
109+
```
110+
111+
## 5. Catch and report Dart errors
112+
113+
Now that we have a function to report errors depending on the environment, we
114+
need a way to capture Dart errors so we can report them!
115+
116+
For this task, we will run our app inside a custom
117+
[`Zone`](https://docs.flutter.io/flutter/dart-async/Zone-class.html). Zones
118+
establish an execution context for our code. This provides a convenient way to
119+
capture all errors that occur within that context by providing an `onError`.
120+
121+
In this case, we'll run our app in a new `Zone` and capture all errors by
122+
providing an `onError` callback.
123+
124+
<!-- skip -->
125+
```dart
126+
runZoned<Future<Null>>(() async {
127+
runApp(new CrashyApp());
128+
}, onError: (error, stackTrace) {
129+
// Whenever an error occurs, call the `_reportError` function. This will send
130+
// Dart errors to our dev console or Sentry depending on the environment.
131+
_reportError(error, stackTrace);
132+
});
133+
```
134+
135+
## 6. Catch and report Flutter errors
136+
137+
In addition to Dart errors, Flutter can throw additional errors, such as
138+
platform exceptions that occur when calling native code. We need to be sure to
139+
capture and report these types of errors as well!
140+
141+
To capture Flutter errors, we can override the
142+
[`FlutterError.onError`](https://docs.flutter.io/flutter/foundation/FlutterError/onError.html)
143+
property. In this case, if we're in debug mode, we'll use a convenience function
144+
from Flutter to properly format the error. If we're in production mode, we'll
145+
send the error to our `onError` callback defined in the previous step.
146+
147+
<!-- skip -->
148+
```dart
149+
// This captures errors reported by the Flutter framework.
150+
FlutterError.onError = (FlutterErrorDetails details) {
151+
if (isInDebugMode) {
152+
// In development mode simply print to console.
153+
FlutterError.dumpErrorToConsole(details);
154+
} else {
155+
// In production mode report to the application zone to report to
156+
// Sentry.
157+
Zone.current.handleUncaughtError(details.exception, details.stack);
158+
}
159+
};
160+
```
161+
162+
## Complete Example
163+
164+
To view a working example, please view the
165+
[Crashy](https://github.com/flutter/crashy) example app.
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
---
2+
layout: page
3+
title: "Navigate with named routes"
4+
permalink: /cookbook/networking/named-routes/
5+
---
6+
7+
In the
8+
[Navigate to a new screen and back](/cookbook/navigation/navigation-basics/)
9+
recipe, we learned how to Navigate to a new screen by creating a new route and
10+
pushing it to the
11+
[`Navigator`](https://docs.flutter.io/flutter/widgets/Navigator-class.html).
12+
13+
However, if we need to navigate to the same screen in many parts of our apps,
14+
this can result in code duplication. In these cases, it can be handy to define
15+
a "named route," and use the named route for Navigation.
16+
17+
To work with named routes, we can use the
18+
[`Navigator.pushNamed`](https://docs.flutter.io/flutter/widgets/Navigator/pushNamed.html)
19+
function. This example will replicate the functionality from the original
20+
recipe, demonstrating how to use named routes instead.
21+
22+
## Directions
23+
24+
1. Create two screens
25+
2. Define the routes
26+
3. Navigate to the second screen using `Navigator.pushNamed`
27+
4. Return to the first screen using `Navigator.pop`
28+
29+
## 1. Create two screens
30+
31+
First, we'll need two screens to work with. The first screen will contain a
32+
button that navigates to the second screen. The second screen will contain a
33+
button that navigates back to the first.
34+
35+
```dart
36+
class FirstScreen extends StatelessWidget {
37+
@override
38+
Widget build(BuildContext context) {
39+
return new Scaffold(
40+
appBar: new AppBar(
41+
title: new Text('First Screen'),
42+
),
43+
body: new Center(
44+
child: new RaisedButton(
45+
child: new Text('Launch new screen'),
46+
onPressed: () {
47+
// Navigate to second screen when tapped!
48+
},
49+
),
50+
),
51+
);
52+
}
53+
}
54+
55+
class SecondScreen extends StatelessWidget {
56+
@override
57+
Widget build(BuildContext context) {
58+
return new Scaffold(
59+
appBar: new AppBar(
60+
title: new Text("Second Screen"),
61+
),
62+
body: new Center(
63+
child: new RaisedButton(
64+
onPressed: () {
65+
// Navigate back to first screen when tapped!
66+
},
67+
child: new Text('Go back!'),
68+
),
69+
),
70+
);
71+
}
72+
}
73+
```
74+
75+
## 2. Define the routes
76+
77+
Next, we'll need to define our routes by providing additional properties to the
78+
[`MaterialApp`](https://docs.flutter.io/flutter/material/MaterialApp-class.html)
79+
constructor: the `initialRoute` and the `routes` themselves.
80+
81+
The `initialRoute` property defines which route our app should start with. The
82+
`routes` property defines the available named routes and the Widgets that should
83+
be built when we navigate to those routes.
84+
85+
<!-- skip -->
86+
```dart
87+
new MaterialApp(
88+
// Start the app with the "/" named route. In our case, the app will start
89+
// on the FirstScreen Widget
90+
initialRoute: '/',
91+
routes: {
92+
// When we navigate to the "/" route, build the FirstScreen Widget
93+
'/': (context) => new FirstScreen(),
94+
// When we navigate to the "/second" route, build the SecondScreen Widget
95+
'/second': (context) => new SecondScreen(),
96+
},
97+
);
98+
```
99+
100+
Note: When using `initialRoute`, be sure you do not define a `home` property.
101+
102+
## 3. Navigate to the second screen
103+
104+
With our Widgets and routes in place, we can begin navigating! In this case,
105+
we'll use the
106+
[`Navigator.pushNamed`](https://docs.flutter.io/flutter/widgets/Navigator/pushNamed.html)
107+
function. This tells Flutter to build the Widget defined in our `routes` table
108+
and launch the screen.
109+
110+
In the `build` method of our `FirstScreen` Widget, we'll update the `onPressed`
111+
callback:
112+
113+
<!-- skip -->
114+
```dart
115+
// Within the `FirstScreen` Widget
116+
onPressed: () {
117+
// Navigate to the second screen using a named route
118+
Navigator.pushNamed(context, '/second');
119+
}
120+
```
121+
122+
## 4. Return to the first screen
123+
124+
In order to navigate back to the first page, we can use the
125+
[`Navigator.pop`](https://docs.flutter.io/flutter/widgets/Navigator/pop.html)
126+
function.
127+
128+
<!-- skip -->
129+
```dart
130+
// Within the SecondScreen Widget
131+
onPressed: () {
132+
// Navigate back to the first screen by popping the current route
133+
// off the stack
134+
Navigator.pop(context);
135+
}
136+
```
137+
138+
## Complete Example
139+
140+
```dart
141+
import 'package:flutter/material.dart';
142+
143+
void main() {
144+
runApp(new MaterialApp(
145+
title: 'Named Routes Demo',
146+
// Start the app with the "/" named route. In our case, the app will start
147+
// on the FirstScreen Widget
148+
initialRoute: '/',
149+
routes: {
150+
// When we navigate to the "/" route, build the FirstScreen Widget
151+
'/': (context) => new FirstScreen(),
152+
// When we navigate to the "/second" route, build the SecondScreen Widget
153+
'/second': (context) => new SecondScreen(),
154+
},
155+
));
156+
}
157+
158+
class FirstScreen extends StatelessWidget {
159+
@override
160+
Widget build(BuildContext context) {
161+
return new Scaffold(
162+
appBar: new AppBar(
163+
title: new Text('First Screen'),
164+
),
165+
body: new Center(
166+
child: new RaisedButton(
167+
child: new Text('Launch new screen'),
168+
onPressed: () {
169+
// Navigate to the second screen using a named route
170+
Navigator.pushNamed(context, '/second');
171+
},
172+
),
173+
),
174+
);
175+
}
176+
}
177+
178+
class SecondScreen extends StatelessWidget {
179+
@override
180+
Widget build(BuildContext context) {
181+
return new Scaffold(
182+
appBar: new AppBar(
183+
title: new Text("Second Screen"),
184+
),
185+
body: new Center(
186+
child: new RaisedButton(
187+
onPressed: () {
188+
// Navigate back to the first screen by popping the current route
189+
// off the stack
190+
Navigator.pop(context);
191+
},
192+
child: new Text('Go back!'),
193+
),
194+
),
195+
);
196+
}
197+
}
198+
```
199+
200+
![Navigation Basics Demo](/images/cookbook/navigation-basics.gif)

0 commit comments

Comments
 (0)