# flutter/dart カリキュラム ###### tags: `curriculum` `seeds` [TOC] ## 目標 - flutterの動きを一通り理解できるようになる - flutterで簡単なアプリ(Todoアプリ)を作ってみる - dockerの練習 ## 成果物 - Todoアプリの制作 ## 構成 ### 環境構築 #### docker - Dockerfile ``` FROM dart:2.17-sdk # Workdir WORKDIR /app # 2: Install Flutter ARG PATH=/root/.pub-cache/bin:$PATH ARG FLUTTER_VERSION=3.0.1 RUN dart pub global activate melos --verbose && \ dart pub global activate fvm --verbose && \ fvm doctor --verbose && \ fvm install $FLUTTER_VERSION --verbose && \ fvm use --force $FLUTTER_VERSION --verbose && \ fvm flutter config --enable-web --enable-linux-desktop --enable-macos-desktop --enable-windows-desktop --enable-android --enable-ios --enable-fuchsia && \ # fvm flutter precache --verbose && \ fvm flutter doctor --verbose # Set paths ENV FVM_ROOT=/root/.pub-cache ENV PATH $FVM_ROOT/bin:$PATH ``` docker-compose.yml ``` version: "3.8" services: flutter: container_name: flutter build: . volumes: - .:/app ports: - 3000:3000 restart: always stdin_open: true tty: true ``` bash ``` #1行ずつ実行 docker compose build docker compose up -d docker exec -it flutter bash #作成したコンテナにアクセス後 $ fvm use --force 3.0.1 $ fvm flutter create myapp $ cd ./myapp $ fvm flutter run -d web-server --web-port=5555 #localhost:5555にアクセスすると閲覧できる $ exit sudo chmod -R 777 ./todo/lib/main.dart ``` main.dart ``` import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( // This is the theme of your application. // // Try running your application with "flutter run". You'll see the // application has a blue toolbar. Then, without quitting the app, try // changing the primarySwatch below to Colors.green and then invoke // "hot reload" (press "r" in the console where you ran "flutter run", // or simply save your changes to "hot reload" in a Flutter IDE). // Notice that the counter didn't reset back to zero; the application // is not restarted. primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.title}) : super(key: key); // This widget is the home page of your application. It is stateful, meaning // that it has a State object (defined below) that contains fields that affect // how it looks. // This class is the configuration for the state. It holds the values (in this // case the title) provided by the parent (in this case the App widget) and // used by the build method of the State. Fields in a Widget subclass are // always marked "final". final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { // This call to setState tells the Flutter framework that something has // changed in this State, which causes it to rerun the build method below // so that the display can reflect the updated values. If we changed // _counter without calling setState(), then the build method would not be // called again, and so nothing would appear to happen. _counter++; }); } @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done // by the _incrementCounter method above. // // The Flutter framework has been optimized to make rerunning build methods // fast, so that you can just rebuild anything that needs updating rather // than having to individually change instances of widgets. return Scaffold( appBar: AppBar( // Here we take the value from the MyHomePage object that was created by // the App.build method, and use it to set our appbar title. title: Text(widget.title), ), body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: Column( // Column is also a layout widget. It takes a list of children and // arranges them vertically. By default, it sizes itself to fit its // children horizontally, and tries to be as tall as its parent. // // Invoke "debug painting" (press "p" in the console, choose the // "Toggle Debug Paint" action from the Flutter Inspector in Android // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) // to see the wireframe for each widget. // // Column has various properties to control how it sizes itself and // how it positions its children. Here we use mainAxisAlignment to // center the children vertically; the main axis here is the vertical // axis because Columns are vertical (the cross axis would be // horizontal). mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } } ``` ### 画面構築 #### リスト一覧画面 ``` import 'package:flutter/material.dart'; void main() { // 最初に表示するWidget runApp(MyTodoApp()); } class MyTodoApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( // アプリ名 title: 'My Todo App', theme: ThemeData( // テーマカラー primarySwatch: Colors.blue, ), // リスト一覧画面を表示 home: TodoListPage(), ); } } // リスト一覧画面用Widget class TodoListPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text('リスト一覧画面'), ), ); } } ``` #### リスト追加画面 main.dartに追加してみよう. ``` import 'package:flutter/material.dart'; // --- 省略 --- // リスト一覧画面用Widget class TodoListPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text('リスト一覧画面'), ), floatingActionButton: FloatingActionButton( onPressed: () { // "push"で新規画面に遷移 Navigator.of(context).push( MaterialPageRoute(builder: (context) { // 遷移先の画面としてリスト追加画面を指定 return TodoAddPage(); }), ); }, child: Icon(Icons.add), ), ); } } // リスト追加画面用Widget class TodoAddPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: TextButton( // ボタンをクリックした時の処理 onPressed: () { // "pop"で前の画面に戻る Navigator.of(context).pop(); }, child: Text('リスト追加画面(クリックで戻る)'), ), ), ); } } ```