Flutterの公式ドキュメントを読んで理解する 〜Navigation & Routing〜

# 今回読んだページ
https://flutter.dev/docs/cookbook/navigation

# 個人的なまとめ
* 画面遷移は`Navigator.push()`を使う。
* 元の画面に戻るときは`Navigator.pop()`を使う。
* 名前付きRouteに遷移する際は、`Navigator.pushNamed()`を使う。
* 名前付きRouteに遷移するときに引数を渡す場合は、`Navigator.pushNamed()`に`arguments`を指定する
* 引数を読み出すときは、`ModalRoute.of(context).settings.arguments`で取り出す。
* 遷移したあとの画面から、元の画面へデータを渡したい時は、`Navigator.pop()`に第2引数を指定するとできる。

# Animate a widget across screens
多くの場合、ユーザーが画面から画面へと移動するときに、アプリを通じてユーザーをガイドすることが役立ちます。
アプリを通じてユーザーをリードする一般的な技術は、ある画面から次の画面へとWidgetをアニメーションさせることです。
これにより、2つの画面を接続する視覚的なアンカーが作成されます。

`Hero`widgetを使って、ある画面から次の画面へとアニメーションさせます。
このレシピには、以下のステップがあります：

1. 同じ画像を表示する2つの画面を作る
2. 1つ目の画面に`Hero`widgetを追加する
3. 2つ目の画面に`Hero`widgetを追加する

## 1. Create two screens showing the same image
この例では、両方の画面に同じ画像を表示させます。
ユーザーが画像をタップしたとき、1つ目の画面から2つ目の画面へと画像をアニメーションさせます。
ここでは、視覚的な構造を作成し、次のステップでアニメーションを取り扱います。

>Note: この例は、[Navigate to a new screen and back](https://flutter.dev/docs/cookbook/navigation/navigation-basics)と[Handle taps](https://flutter.dev/docs/cookbook/gestures/handling-taps)に基づいて構築されます。

```dart
class MainScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Main Screen'),
      ),
      body: GestureDetector(
        onTap: () {
          Navigator.push(context, MaterialPageRoute(builder: (_) {
            return DetailScreen();
          }));
        },
        child: Image.network(
          'https://picsum.photos/250?image=9',
        ),
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTap: () {
          Navigator.pop(context);
        },
        child: Center(
          child: Image.network(
            'https://picsum.photos/250?image=9',
          ),
        ),
      ),
    );
  }
}
```
## 2. Add a Hero widget to the first screen
2つの画面をアニメーションでつなぐためには、両方のスクリーンの`Image`widgetを`Hero`widgetでラップします。

```dart
Hero(
  tag: 'imageHero',
  child: Image.network(
    'https://picsum.photos/250?image=9',
  ),
);
```

`Hero`widgetは2つの引数を必要とします。

* tag:   `Hero`を識別するオブジェクト。両方の画面で同じである必要があります。
* child: 画面を通してアニメーションするWidget。

## 3. Add a Hero widget to the second screen
1つ目の画面との接続を完了するには、1つ目の画面の`Hero`と同じ`tag`を持った`Hero`widgetで、2つ目の画面の`Image`widgetをラップします。

```dart
Hero(
  tag: 'imageHero',
  child: Image.network(
    'https://picsum.photos/250?image=9',
  ),
);
```
>Note: このコードは1つ目の画面のものと同じです。
ベストプラクティスとしては、コードを繰り返すのではなく、再利用可能なWidgetを作成することです。
この例では、簡単のため、両方のWidgetに同じコードを使用しています。

2つ目の画面に`Hero`widgetを適用すると、画面間のアニメーションが動きます。

## Interactive Example
<p class="codepen" data-height="434" data-theme-id="dark" data-default-tab="js,result" data-user="popy1017" data-slug-hash="PoPLBWe" data-preview="true" style="height: 434px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="flutter-navigate-animate-1">
  <span>See the Pen <a href="https://codepen.io/popy1017/pen/PoPLBWe">
  flutter-navigate-animate-1</a> by popy1017 (<a href="https://codepen.io/popy1017">@popy1017</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# Navigate to a new screen and back
たいていのアプリには、異なるタイプの情報を表示するために、いくつかの画面があります。
例えば、あるアプリが製品を表示するための画面を持っています。
ユーザーが製品の画像をタップすると、製品に関する詳細が新しい画面に表示されます。

>用語: Flutterでは、画面やページをRouteと呼んでいます。このレシピの残りの部分では、Routeと呼びます。
(この記事では、「ルート」と書くとRootと混同するのでRouteと表記します。)

Androidでは、routeはActivityと同等です。
iOSでは、routeはViewControllerと同等です。
Flutterでは、routeはwidgetです。

`Navigator`を使い、新しいrouteに移動します。このレシピは以下のステップを使います。
次の数セクションで、以下の3ステップで2つのroute間を移動するやり方を示します：

1. Create two routes.
2. Navigate to the second route using Navigator.push()
3. Return to the first route using Navigator.pop()

## 1. Create two routes.
まず、使用する2つのrouteを作成します。
これは基本的な例なので、各Routeは1つのボタンのみを含みます。
1つ目のRouteのボタンを押すと、2つ目のRouteに遷移します。
2つ目のRouteのボタンを押すと、1つ目のRouteに帰ります。

まずは、視覚的な構造をセットアップします：

```dart
class FirstRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Route'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('Open route'),
          onPressed: () {
            // Navigate to second route when tapped.
          },
        ),
      ),
    );
  }
}

class SecondRoute extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Route"),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            // Navigate back to first route when tapped.
          },
          child: Text('Go back!'),
        ),
      ),
    );
  }
}
```

## 2. Navigate to the second route using Navigator.push()
新しいRouteに切り替えるためには、`Navigator.push()`メソッドを使います。
`push()`メソッドは、`Navigator`によって管理されるRouteのスタックに`Route`を追加します。
`Route`はどこからくるのでしょうか？
独自に作成するか、`MaterialPageRoute`を使います。
プラットフォーム固有のアニメーションを使って新しいRouteに遷移するので、`MaterialPageRoute`は便利です。

```dart
// Within the `FirstRoute` widget
onPressed: () {
  Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => SecondRoute()),
  );
}
```

## 3. Return to the first route using Navigator.pop()
どうやって2つ目のRouteを閉じて1つ目のRouteに戻るのでしょうか？
`Navigator.pop()`メソッドを使います。
`pop()`メソッドは、`Navigator`によって管理されるRouteのスタックから現在の`Route`を削除します。

元のRouteへ戻るのを実装するには、`SecondRoute`widgetの`onPressed()`コールバックを更新します。

```dart
// Within the SecondRoute widget
onPressed: () {
  Navigator.pop(context);
}
```

## Interactive example
<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="js,result" data-user="popy1017" data-slug-hash="PoPLypb" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="flutter-navigate-animate-2">
  <span>See the Pen <a href="https://codepen.io/popy1017/pen/PoPLypb">
  flutter-navigate-animate-2</a> by popy1017 (<a href="https://codepen.io/popy1017">@popy1017</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# Navigate with named routes
「Navigation to a new screen and back」レシピでは、新しいRouteを作成し、`Navigator`にプッシュすることで、新しい画面に遷移する方法を学びました。

しかし、アプリのたくさんの部分で同じ画面に遷移する必要がある場合、このアプローチはコードの重複を引き起こす可能性があります。
解決策は「名前付きRoute(named Route)」を定義することで、ナビゲーションにそれを使うことです。

名前付きRouteを使うためには、`Navigator.pushNamed()`関数を使います。
この例では元のレシピから機能を複製し、次の手順で名前付きRouteを使用する方法を示します。

1. Create two screens.
2. Define the routes.
3. Navigate to the second screen using `Navigator.pushNamed()`
4. Return to the first screen using `Navigator.pop()`

## 1. Create two screens
```dart
class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('Launch screen'),
          onPressed: () {
            // Navigate to the second screen when tapped.
          },
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Screen"),
      ),
      body: Center(
        child: RaisedButton(
          onPressed: () {
            // Navigate back to first screen when tapped.
          },
          child: Text('Go back!'),
        ),
      ),
    );
  }
}
```
## 2. Define the routes
次に、`MaterialApp`コンストラクタに追加のプロパティ(`initialRoute`と`routes`)を設定して、Routeを定義します。

`initialRoute`プロパティは、アプリがどのRouteで始まるかを定義します。
`route`プロパティは、設定したRouteにナビゲートする際に利用可能な名前付きRouteとビルドするWidgetを定義します。

```dart
MaterialApp(
  // "/"と名前が付けられたRouteからスタートする。
  // この場合、アプリはFirstScreen widgetからスタートする
  initialRoute: '/',
  routes: {
    // "/" routeにナビゲートするときは、FirstScreen widgetをビルドする
    '/': (context) => FirstScreen(),
    // "/second"routeにナビゲートするときは、SecondScrennをビルドする
    '/second': (context) => SecondScreen(),
  },
);
```
>警告: `initialRoute`を使うときは、`home`プロパティは定義しないでください。

## 3. Navigate to the second screen
WidgetとRouteを配置したら、`Navigator.pushNamed()`メソッドを使ってナビゲーションをトリガーします。
`routes`テーブルで定義されたWidgetを作成して画面を起動するようにFlutterに指示します。

`FirstScreen`widgetの`build()`メソッドの`onPressed()`を更新します：

```dart
// Within the `FirstScreen` widget
onPressed: () {
  // Navigate to the second screen using a named route.
  Navigator.pushNamed(context, '/second');
}
```

## 4. Return to the first screen
1つ目の画面に戻るためには、`Navigation.pop()`を使います。

```dart
// Within the SecondScreen widget
onPressed: () {
  // Navigate back to the first screen by popping the current route
  // off the stack.
  Navigator.pop(context);
}
```
# Pass arguments to a named route
`Navigator`は共通の識別子を使ってアプリのどこからでも名前付きRouteにナビゲートする機能を提供します。
ある場合には、名前付きRouteに引数を渡す必要があるかもしれない。
例えば、`/user`routeに遷移して、そのrouteにユーザーに関する情報を渡したいかもしれない。

`Navigator.pushNamed()`メソッドの`arguments`パラメータを使うことでこれを達成できる。
`ModalRoute.of()`メソッドを使用するか、`MaterialApp`や`CupertinoApp`コンストラクタが提供する`onGenerateRoute()`の中から引数を抽出する。

このレシピは、以下のステップにしたがって、名前付きRouteへの引数の渡し方と`ModalRoute.of()`と`onGenerateRoute()`を使った引数の読み出し方を示す：

1. Define the arguments you need to pass.
2. Create a widget that extracts the arguments.
3. Register the widget in the `routes` table.
4. Navigate to the widget.

## 1. Define the arguments you need to pass.
まず、新しいRouteに渡す必要がある引数を定義する。
この例では、2つのデータを渡す。： 画面の`title`と、`message`

両方のデータを渡すために、この情報を保持するClassを作成する。

```dart
// arguments parameterにはどんなオブジェクトでも渡せる
// この例では、カスタマイズ可能なtitleとmessageを含んだClassを作る
class ScreenArguments {
  final String title;
  final String message;

  ScreenArguments(this.title, this.message);
}
```

## 2. Create a widget that extracts the arguments
次に、`ScreenArguments`から`title`と`message`を抽出して表示するWidgetを作成します。
`ScreenArguments`にアクセスするためには、`ModalRoute.of()`メソッドを使います。
このメソッドは引数を持った現在のRouteを返します。

```dart
// ModalRouteから必要な引数を抽出するWidget
class ExtractArgumentsScreen extends StatelessWidget {
  static const routeName = '/extractArguments';

  @override
  Widget build(BuildContext context) {
    // 現在のModalRoute settingsから引数を抽出し、ScreenArgumentsにキャストする
    final ScreenArguments args = ModalRoute.of(context).settings.arguments;

    return Scaffold(
      appBar: AppBar(
        title: Text(args.title),
      ),
      body: Center(
        child: Text(args.message),
      ),
    );
  }
}
```
## 3. Register the widget in the routes table
次に、`MaterialApp`widgetに提供された`routes`にエントリーを追加します。
`routes`はRouteの名前に基づいて作成されるべきWidgetを定義します。

```dart
MaterialApp(
  routes: {
    ExtractArgumentsScreen.routeName: (context) => ExtractArgumentsScreen(),
  },
);
```
## 4. Navigate to the widget
最後に、ユーザーがボタンをタップしたときに、`Navigator.pushNamed()`を使って、`ExtractArgumentsScreen`にナビゲートする。
`arguments`プロパティを介してRouteに引数を渡します。
`ExtractArgumentsScreen`はこれらの引数から`title`と`message`を抽出します。

```dart
// 名前付きRouteにナビゲートするボタン。
// 名前付きRouteは自身で引数を抽出する。
RaisedButton(
  child: Text("Navigate to screen that extracts arguments"),
  onPressed: () {
    // ユーザーがボタンをタップしたとき、名前付きRouteにナビゲートして、
    // 任意のパラメータとして引数を渡す
    Navigator.pushNamed(
      context,
      ExtractArgumentsScreen.routeName,
      arguments: ScreenArguments(
        'Extract Arguments Screen',
        'This message is extracted in the build method.',
      ),
    );
  },
),
```

### Alternatively, extract the arguments using `onGenerateRoute`
Widget内で直接引数を抽出する代わりに、`onGenerateRoute()`関数の中で引数を抽出し、Widgetに渡すこともできる。

`onGenerateRoute()`関数は、与えられた`RouteSettings`に基づいて正しいRouteを作成する。

```dart
MaterialApp(
  // 名前付きRouteを扱う関数を提供する。この関数を使い、
  // プッシュされた名前付きRouteを特定し、正しい画面を作成する
  onGenerateRoute: (settings) {
    // PassArguments routeをプッシュした場合
    if (settings.name == PassArgumentsScreen.routeName) {
      // 正しい型(ScreenArguments)に引数をキャストする
      final ScreenArguments args = settings.arguments;

      // そして、引数から必要なデータを抽出し、
      // 正しい画面にデータを渡す
      return MaterialPageRoute(
        builder: (context) {
          return PassArgumentsScreen(
            title: args.title,
            message: args.message,
          );
        },
      );
    }
  },
);
```
## Interactive example
<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="js,result" data-user="popy1017" data-slug-hash="ZEbPwbx" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="flutter-navigate-animate-3">
  <span>See the Pen <a href="https://codepen.io/popy1017/pen/ZEbPwbx">
  flutter-navigate-animate-3</a> by popy1017 (<a href="https://codepen.io/popy1017">@popy1017</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# Return data from a screen
ある場合では、新しい画面からデータを返したいかもしれない。
例えば、ユーザーに対する2つのオプションがある新しい画面をプッシュするとします。
ユーザーがオプションをタップしたとき、ユーザーの選択を1つ目の画面に知らせて、その情報に基づいてアクションできるようにする必要があります。

以下の手順で、`Navigator.pop()`メソッドを使うことでこれを行うことができます。

1. Define the home screen
2. Add a button that launches the selection screen
3. Show the selection screen with two buttons 
4. When a button is tapped, close the selection screen
5. Show a snackbar on the home screen with the selection

## 1. Define the home screen
ホーム画面はボタンを表示します。タップされると、選択画面を起動します。

```dart
class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Returning Data Demo'),
      ),
      // 次のステップでSelectionButton widgetを作成する
      body: Center(child: SelectionButton()),
    );
  }
}
```

## 2. Add a button that launches the selection screen
次に、次のようなSelectionButtonを作成します。

* タップされたときにSelectionScreenが起動します。
* SelectionScreenが結果を返すのを待ちます。

```dart
class SelectionButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: () {
        _navigateAndDisplaySelection(context);
      },
      child: Text('Pick an option, any option!'),
    );
  }

  // SelectionScreenを起動し、Navigator.popからの結果を待つメソッド
  _navigateAndDisplaySelection(BuildContext context) async {
    // Navigator.pushは、SelectionScreenでNavigator.popが呼ばれたあとに
    // 完了するFutureを返す。
    final result = await Navigator.push(
      context,
      // Create the SelectionScreen in the next step.
      MaterialPageRoute(builder: (context) => SelectionScreen()),
    );
  }
}
```
## 3. Show the selection screen with two buttons
次に、2つのボタンを含む選択画面を作成する。
ユーザーがボタンをタップしたとき、アプリは選択画面を閉じ、ホーム画面にどのボタンがタップされたかを知らせる。

このステップはUIを定義する。次のステップで、データを返すコードを追加する。

```dart
class SelectionScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Pick an option'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  // Pop here with "Yep"...
                },
                child: Text('Yep!'),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                onPressed: () {
                  // Pop here with "Nope"
                },
                child: Text('Nope.'),
              ),
            )
          ],
        ),
      ),
    );
  }
}
```

## 4. When a button is tapped, close the selection screen
次に、両方のボタンの`onPressed()`コールバックを更新する。
最初の画面にデータを返すためには、`result`と呼ばれる2番目の任意の引数を受け付ける`Navigator.pop()`メソッドを使います。
結果はSelectionButtonで`Future`に返されます。

```dart:Yep_button
RaisedButton(
  onPressed: () {
    // The Yep button returns "Yep!" as the result.
    Navigator.pop(context, 'Yep!');
  },
  child: Text('Yep!'),
);
```

```dart:Nope_button
RaisedButton(
  onPressed: () {
    // The Nope button returns "Nope!" as the result.
    Navigator.pop(context, 'Nope!');
  },
  child: Text('Nope!'),
);
```

## 5. Show a snackbar on the home screen with the selection
選択画面を起動し結果を待機しているので、返却された情報で何かを実行する必要があります。

この場合、`SelectionButton`の中で`_navigateAndDisplaySelection()`メソッドを使って、結果を表示するスナックバーを表示します。

```dart
_navigateAndDisplaySelection(BuildContext context) async {
  final result = await Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => SelectionScreen()),
  );

  // 選択画面が結果を返したあと、前のスナックバーを隠し、新しい結果を表示する
  Scaffold.of(context)
    ..removeCurrentSnackBar()
    ..showSnackBar(SnackBar(content: Text("$result")));
}
```

### 補足
上記のコードの最終行で登場する`..`はCascade表記と呼ばれるDart固有の書き方？である。
[Language tour | Dart](https://dart.dev/guides/language/language-tour#cascade-notation-)
Cascadeを使うと、同じオブジェクトにたいして一連の操作を行うことができる。
関数呼び出しだけでなく、オブジェクトのフィールドにもアクセスすることができる。
以下のCascadeあり・なしのコードを見るとわかるが、一時的な変数を用意する必要がなくなるので、コードをきれいにすることができる。

```dart:通常の書き方
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
```

```dart:Cascade表記
querySelector('#confirm') // Get an object.
  ..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));
```


# Send data to a new screen
多くの場合、新しい画面に移動するだけでなく、画面いデータも渡したいでしょう。
例えば、タップされたアイテムの情報を渡したいかもしれません。

画面は単なるWidgetであることを覚えてください。
この例では。Todoリストを作ります。
Todoがタップされると、そのTodoに関する情報が表示される新しい画面(Widget)に遷移します。
このレシピは以下の手順で行われます：

1. Define a todo class.
2. Display a list of todos.
3. Create a detail screen that can display information about a todo.
4. Navigate and pass data to the detail screen.

## 1. Define a todo class
まず、Todoを表すシンプルな方法が必要です。
この例では、titleとdescriptionの2つのデータを含むクラスを作成します。

```dart
class Todo {
  final String title;
  final String description;

  Todo(this.title, this.description);
}
```

## 2. Create a list of todos
次に、Todoのリストを表示します。
この例では、20個のTodoを作成し、ListViewを使って表示します。
リストの操作に関しては[Use lists recipe](https://flutter.dev/docs/cookbook/lists/basic-list)を参照してください。

### Generate the list of todos
```dart
final todos = List<Todo>.generate(
  20,
  (i) => Todo(
    'Todo $i',
    'A description of what needs to be done for Todo $i',
  ),
);
```

## Display the list of todos using a ListView
```dart
ListView.builder(
  itemCount: todos.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(todos[index].title),
    );
  },
);
```
ここまでは順調です。これで、20個のTodoが作成され、ListViewに表示されます。

## 3. Create a detail screen to display information about a todo
次に、2つ目の画面を作成します。
画面のタイトルはTodoのタイトルを含み、画面のbodyには説明が表示されます。

詳細画面は通常の`StatelessWidget`であるため、ユーザーにUIでTodoの入力を要求します。
そして、与えられたTodoを使ってUIを構築します。

```dart
class DetailScreen extends StatelessWidget {
  // Todoを保持するフィールドを宣言する
  final Todo todo;

  // コンストラクタでは、Todoを必須とする。
  // In the constructor, require a Todo.
  DetailScreen({Key key, @required this.todo}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Todoを使ってUIを作成する
    return Scaffold(
      appBar: AppBar(
        title: Text(todo.title),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Text(todo.description),
      ),
    );
  }
}
```

## 4. Navigate and pass data to the detail screen
`DetailScreen`を配置したら、ナビゲーションを行う準備ができました。
この例では、ユーザーがリストのTodoをタップすると`DetailScreen`に遷移します。
`DetailScreen`にはTodoを渡します。

ユーザーのタップをキャプチャするには、`ListTile`widgetで`onTap()`コールバックを書きます。
`onTap()`コールバックの中で、`Navigator.push()`関数を使います。

```dart
ListView.builder(
  itemCount: todos.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(todos[index].title),
      // ユーザーがタップしたら、DetailScreenに遷移する
      // DetailScreenを作成しているだけでなく、
      // 現在のTodoを渡していることに注意してください
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => DetailScreen(todo: todos[index]),
          ),
        );
      },
    );
  },
);
```

## Interactive example
<p class="codepen" data-height="265" data-theme-id="dark" data-default-tab="js,result" data-user="popy1017" data-slug-hash="yLYrMwb" data-preview="true" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="flutter-navigate-animate-4">
  <span>See the Pen <a href="https://codepen.io/popy1017/pen/yLYrMwb">
  flutter-navigate-animate-4</a> by popy1017 (<a href="https://codepen.io/popy1017">@popy1017</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>