Flutterラボ リリース3周年記念! プロコースが1ヶ月の料金で2ヶ月間受けられる、実質半額セール (2024年3月17日まで・先着)

Flutterラボの

プレミアム会員になる

スターター教材【パスワードポスト】

2020.06.07

〜Password post〜

-Hatchout school-

株式会社井雄
編集者: 奥野領太

はじめに

開発する環境が整いました。これから実際のプログラミングに入っていきます。
スターターでは、Dartの簡単な記述やFlutterの扱い方を学んで頂きます。
それでは始めていきましょう!

※こちらの内容は動画でも解説しております。こちらの記事と合わせて学習を進めて行って下さい!

Flutter超入門! スターター教材解説 ハッチアウトスクールでは、Flutterを直接学べるスクールの運営もしております。 詳しくは下記リンクより。お気軽にお問い www.youtube.com
【Flutter】3日でできるFlutterアプリ開発入門 環境構築から簡単なアプリの開発を通じてFlutterを最短"3日"で学習 www.udemy.com

【1】コメントアウトの削除

コメントアウトとは、コードの中に書けるメモのようなものです。
下記のように薄いグレー担っている箇所がコメントアウトです。

// This widget is the root of your application.

「//」の右側に書いた文字はプログラムが無視するため、メモとして残すことができます。
英語で書かれたコメントアウトがたくさんあって見にくくなるので全て削除してください。

} や ) の右側にあるコメントアウトは削除できないため無視してください。
(削除できないコメントアウトの例)

), // AppBar

【2】ToDoリストを作成する

まずは、ToDoリストを作成してみましょう。
//todoと記述することにより、プログラムに影響を与えず、タスクを分かりやすく管理することができます。

現在記述されているコードの一番下に、このように記述してみましょう。


 floatingActionButton: FloatingActionButton(
       onPressed: _incrementCounter,
       tooltip: 'Increment',
       child: Icon(Icons.add),
     ),
   );
 }
}

//todo ホーム画面のタイトル変更
//todo リスト表示
//todo リスト間に区切り線表示
//todo リスト表示を動的に
//todo フロートアクションボタンをタップしたときに、リストにひとつ追加
//todo 新しい画面を作成し、リストをタップしたときに遷移
//todo 新しい画面のレイアウトを作成
//todo 新しい画面にリストからデータを引き継ぐ

【3】タイトル変更

シミュレーター画面上部の文字を「パスワード管理」に変更しましょう。
編集するファイルは、main.dartの下記の箇所です。


appBar: AppBar(
 title: Text(widget.title),
), // AppBar

これを下記のように変更してください。


appBar: AppBar(
 title: Text("パスワード管理"),
),

下の画像のようになっていればOKです。

タイトル変更

Widgetとは

画面に表示するための部品のようなものと考えてください。
名前の一番初めの文字が大文字で記述されます。
今回は、上部の水色の部分が「AppBar」ウィジェットです。

Propertyとは

ウィジェットの具体的な表示を設定する項目です。
名前の一番初めの文字が小文字で記述されます。
今回は、「AppBar」ウィジェットの、「title」プロパティを設定して、「パスワード管理」と表示させました。

※注意

ウィジェットやプロパティの記述の最後には必ず,(カンマ)を入れましょう。
これが抜けているとエラーが発生してしまいます。

appBar: AppBar(
 title: Text("パスワード管理"),//カンマを忘れずに!
),//カンマを忘れずに!

【4】リスト表示

下のように、Amazon,楽天,Yahoo!とリスト表示させましょう。

リスト表示

下のように、Amazon,楽天,Yahoo!とリスト表示させましょう。

body: Center(
 child: Column(
   mainAxisAlignment: MainAxisAlignment.center,
   children: <Widget>[
     Text(
       'You have pushed the button this many times:',
     ),
     Text(
       '$_counter',
       style: Theme.of(context).textTheme.display1,
     ),
   ],
 ),
),

上記の編集箇所から body: 以外を消して、下記のようにしてしまいましょう。

body: ,

この状態では、エラーが出ているはずです。
error: Expected an identifier.というエラーであれば気にせず進んでください。

それ以外のエラーが出ている場合は、消す箇所が間違っています。


body: 以外の削除ができたら、下記のように記述してください。

body: ListView( //【ListView】複数のウィジェットを表示できるウィジェット。画面からはみ出たらスクロールで表示できる。 
 children: <Widget>[
   ListTile( //【ListTile】リストの1項目を表示するためのウィジェット
     leading: Icon(Icons.security),
     //(leading)左端に何を表示するかを決めるプロパティ
     title: Text("Amazon"), //(title)項目のタイトルを決めるプロパティ
   ),
   ListTile(
     leading: Icon(Icons.security),
     title: Text("楽天"),
   ),
   ListTile(
     leading: Icon(Icons.security),
     title: Text("Yahoo!"),
   ),
 ],
),

ListView Widgetとは

ListViewウィジェットには複数のウィジェットを入れることができて、画面からはみ出した分はスクロールして表示してくれます。
ListViewウィジェットのchildrenプロパティには、3つのListTileウィジェットが入っています。
ListTileウィジェットのleadingプロパティは左端に何を表示するかを決めます。今回はsecurityアイコンを表示しています。
ListTileウィジェットのtitleプロパティは項目のタイトルを決めます。今回はそれぞれに「Amazon」「楽天」「Yahoo!」と設定しています。

リスト表示解説

【5】リスト間に区切り線を入れる

下のように、リスト間に区切り線を入れてみましょう。

スクリーンショット 2020-05-11 16.22.23

編集するとこの様になります。

body: ListView(
 children: <Widget>[
   ListTile(
     leading: Icon(Icons.security),
     title: Text("Amazon"),
   ),
   Divider(),//区切り線を表示してくれるWidget。
   ListTile(
     leading: Icon(Icons.security),
     title: Text("楽天"),
   ),
   Divider(),
   ListTile(
     leading: Icon(Icons.security),
     title: Text("Yahoo!"),
   ),
   Divider(),
 ],
),

Divider Widgetとは

画面いっぱいに横線を引いてくれるWidget。
主に、今回のようにWidget間に区切りをつけたいときに利用する。
colorプロパティにて色を変更したり、thicknessプロパティにて太さを変更することも可能。(詳しくは自身で調べてみて下さい。)

【6】リスト表示を動的に

今回のパスワード管理アプリやタスク管理アプリなどでは、リストを増やしたり、減らしたりすることが必要になってきます。

こういった、あらかじめ”何を”、”どれくらい”リスト表示する必要があるかわからない場合に使うのがListView.builder Widgetです。

※このように状況によって形を変えることができるシステムを”動的”と言い、逆に状況によって全く変わらないシステムを”静的”と言います。

まず初めに、リスト表示されているタイトル(今でいうAmazonや楽天のこと)を1箇所にまとめた箱を作りましょう。この箱のことを配列と呼びます。
配列はこのように定義します。

List<型名> 名前 = [要素1, 要素2, 要素3, .......];

今回の例で言うと、以下のようになります。こちらの変数定義をState内にて行います

List<String> titleList = ['Amazon', '楽天', 'Yahoo!'];

この配列の中の要素を取り出すときは以下のように記述します。

print(titleList[0]);//titleListの1つ目の要素を取り出す。
print(titleList[1]);//titleListの2つ目の要素を取り出す。
print(titleList[2]);//titleListの3つ目の要素を取り出す。
//結果
Amazon
楽天
Yahoo!

この知識を使い、ListTileのtitleに設定されているTextを、titleListを用いて表示してみましょう。

編集するとこの様になります。

body: ListView(
 children: <Widget>[
   ListTile(
     leading: Icon(Icons.security),
     title: Text(titleList[0]),
   ),
   Divider(),
   ListTile(
     leading: Icon(Icons.security),
     title: Text(titleList[1]),
   ),
   Divider(),
   ListTile(
     leading: Icon(Icons.security),
     title: Text(titleList[2]),
   ),
   Divider(),
 ],
),

以下の様に、表示結果に変わりがなければ正解です。

リスト表示

ここからList表示を動的にしていく作業に入ります。
ここで使うのがListView.builder Widgetです。このWidgetは以下の様な記述で使っていきます。

body: ListView.builder(
 itemCount: int,  //繰り返す回数を指定
 itemBuilder: (BuildContext context, int index) {
   return Widget; //繰り返し行う処理を記述
 },
),

ここで次にチェックするのが、itemBuilderに入れる繰り返したい処理は何なのか?と言うことです。

今現状はこの様な記述になっているかと思います。

body: ListView(
 children: <Widget>[
   ListTile(
     leading: Icon(Icons.security),
     title: Text(titleList[0]),
   ),
   Divider(),
   ListTile(
     leading: Icon(Icons.security),
     title: Text(titleList[1]),
   ),
   Divider(),
   ListTile(
     leading: Icon(Icons.security),
     title: Text(titleList[2]),
   ),
   Divider(),
 ],
),

この段階で似た様な記述を繰り返している箇所を見つけて下さい。

答えは、この箇所です。

ListTile(
 leading: Icon(Icons.security),
 title: Text(titleList[0]),
),
Divider(),

ListTileのtitleのTextの中身が違うだけで、あとは同じ内容を記述しているのがわかるかと思います。
今回itemBuilderで繰り返す処理はこちらの表示内容です。

ListView.builderのitemBuilderの中にこちらの記述を入れるとこの様になります。

body: ListView.builder(
 itemBuilder: (BuildContext context, int index) {
   return  ListTile(
     leading: Icon(Icons.security),
     title: Text(titleList[0]),
   ),
   Divider(),//Dead codeエラーが発生
 },
),

この様に記述をすると、Divider()にDead codeと言うエラーが発生します。
その理由は、returnで返せるWidgetは1個だけだからです。なので、今の記述で処理されているのはListTileだけとなります。

ですが、今繰り返したいのはDivider()までなので、この2つのWidgetをまとめられる1つのWidgetを用意する必要があります。

そこで今回使うのがColumn Widgetです。

Column Widgetとは

children Propertyに入っている複数のWidgetを縦に並べて表示するWidget。

Column Widgetを用いることで、ListTileとDivider、これら2つのWidgetをまとめることができます。

body: ListView.builder(
 itemBuilder: (BuildContext context, int index) {
   return  Column(
     children: <Widget>[
       ListTile(
         leading: Icon(Icons.security),
         title: Text(titleList[0]),
       ),//ListTile
       Divider(),//Divider
     ]//<widget>[]
   );//Column
 },
),//ListView.builder

次は、itemCount Propertyによって繰り返す回数を指定していきます。
今の段階だと表示するのは3つだけなので、3と指定してあげても良いのですが、今後表示するリストの数の増減が予測される為、今回はtitleList.lengthと指定します。

配列名.lengthでその配列の要素の数をintで得ることができます。

print(titleList.length);
//結果
3

itemCountを設定するとこの様になります。

スクリーンショット 2020-05-11 16.09.13

body: ListView.builder(
 itemBuilder: (BuildContext context, int index) {
   return  Column(
     children: <Widget>[
       ListTile(
         leading: Icon(Icons.security),
         title: Text(titleList[0]),
       ),//ListTile
       Divider(),//Divider
     ]//<widget>[]
   );//Column
 },
 itemCount: titleList.length,//titleListの要素の数
),//ListView.builder

これでtitleListの数だけリストを表示することができました。
ですが、今のままだとリストのタイトルが全てAmazonになってしまっています。
これは繰り返し処理の中のTextがtitleList[0]のままだからです。
これを解消するのがitemBuilder Propertyにて設定したint indexと言う変数です。
indexを用いる事によって、リストのタイトルを動的に変えていくことが可能になります。
(1番目、2番目、3番目、4番目........、index番目と扱えるようになる。)
記述は以下のようになります。

body: ListView.builder(
 itemBuilder: (BuildContext context, int index) {
   return  Column(
     children: <Widget>[
       ListTile(
         leading: Icon(Icons.security),
         title: Text(titleList[index]),
         //indexは0から始まり、繰り返される度に+1される変数
       ),//ListTile
       Divider(),//Divider
     ]//<widget>[]
   );//Column
 },
 itemCount: titleList.length,
),//ListView.builder

ここまでで、リスト表示を動的に変更する工程は完了です。

完成形はこちら。

スクリーンショット 2020-05-11 16.22.23

【7】フロートアクションボタンをタップ時に、リストを一つ追加表示

今回は、右下の+ボタンをタップした時に、新しく『Google』と言うリストが表示されるようにしてみましょう。

スクリーンショット 2020-05-11 16.46.45

今は、titleListに入っている要素を取り出す事によってリスト表示を実現しています。
つまり、ボタンをタップする度に、titleListに新しく『Google』という要素を追加できるようにしていきます。

こちらは配列名.add(新しい要素);で実現することができます。

List<String> titleList = ['Amazon', '楽天', 'Yahoo!'];

titleList.add('Google');//['Amazon', '楽天', 'Yahoo!', 'Google']

ボタンタップ時の処理内容は、FloatingActionButton WidgetのonPressed Propertyに記述します。

記述すると、下記のようになります。

floatingActionButton: FloatingActionButton(
onPressed: () {
  titleList.add('Google');
},
tooltip: 'Increment',
child: Icon(Icons.add),
),

これで完成かと思いきや、ボタンをタップしても『Google』と言う新たなリストは出てこないかと思います。
この処理の時点で、titleListに新たな要素は追加されているはずです。

試しにtitleListをprintしてみましょう。printを使う事によって動作の確認をすることができます。

floatingActionButton: FloatingActionButton(
onPressed: () {
  titleList.add('Google');
  print(titleList);//コンソールに出力することで動作しているか確認
},
tooltip: 'Increment',
child: Icon(Icons.add),
),

するとボタンを押す度にAndroidStudioのRun画面にtitleListの内容が出力されます。そこにGoogleが追加されていれば問題ないです。

スクリーンショット 2020-05-11 17.08.42

画面にリスト表示するにはどうすれば良いのか?
それは、要素が追加された最新の状態のtitleListを画面に表示する為に、画面情報をbuildし直すことで解決できます。

画面情報をbuildし直すには、setStateメソッドを使います。
コードを追加すると以下のようになります。


floatingActionButton: FloatingActionButton(
onPressed: () {
  titleList.add('Google');
  setState(() {
  });//画面情報を再build
},
tooltip: 'Increment',
child: Icon(Icons.add),
),

これにてFloatingActionButtonをタップした時に、リストを1つ追加表示する工程が完了です。

【8】新しい画面を作成し、リストタップ時に遷移

リストをタップした時に、別の画面に移動できるようにしましょう。
画面のレイアウトはあとで行うので、今はどんな状態でも大丈夫です。

スクリーンショット 2020-05-11 17.53.28

まず初めに、新しいDart Fileを作成します。新しく表示させる画面のレイアウトはこちらに記述していきます。

今回はnext_pageと言うFile名にしておきます。
(今まで通りmain.dartに記述して進めていくことも可能ですが、記述が多くなり見にくくなる恐れがあるので、今回はFileを分けて管理する方法で進めていきます。)

スクリーンショット 2020-05-11 17.48.15

新しく作成したnext_page.dartに新たなStatefulWidgetを作成します。
stfと入力すると、stfulと言う予測が出てくるのでこちらを選択すると簡単にStatefulWidgetを作成することができます。

スクリーンショット 2020-05-11 18.06.43

Class名を今回は、NextPageとしておきます。

スクリーンショット 2020-05-11 18.09.53

現段階だと、上画像のようにStatefulWidgetなどにエラーが出ているかと思います。
StatefulWidgetを選択した状態で、『Alt』+『Enter』を押す事によって、Importが必要なパッケージを教えてくれます。

スクリーンショット 2020-05-11 18.13.43

今回はmaterial.dartと言うパケージをimportする事でエラーを解消することができます。

スクリーンショット 2020-05-11 18.13.52

今デフォルトでは、buildメソッドのreturnの返す値が空のContainerになっています。
遷移前の画面と表示が同じになるよう、ここを下記のように変更しておきましょう。

Widget build(BuildContext context) {
 return Scaffold(
   appBar: AppBar(), 
 );//Scaffold
}

ここまでで遷移後のページの準備は完了です。

次は遷移する為の記述をmain.dartに書いていきます。

まず、リストをタップした時の処理は、ListTileのonTap Propertyに記述していきます。


ListTile(
 title: Text(titleList[index]),
 leading: Icon(Icons.vpn_key),
 onTap: () {
 //タップした時の処理をここに記述
 },
),

記述する内容としては、別の画面に遷移すると言うものです。
画面遷移に関しての記述はNavigator.pushにて行います。
(Navigatorの使い方は色々あります。ご自身でも一度調べて見てください。)

記述方法は下記のようになります。


Navigator.push(context, MaterialPageRoute(builder: (context) => 遷移後の画面のStatefulWidgetのClass名()));

ここまでをまとめると、下記のコードになります。

ListTile(
 title: Text(titleList[index]),
 leading: Icon(Icons.vpn_key),
 onTap: () {
   Navigator.push(
     context, MaterialPageRoute(builder: (context) => NextPage())
   );
 },
),

これにてリストタップ時の画面遷移は完了です。

【9】新しい画面のレイアウトを作成

新しい画面のレイアウトを作成していきます。下記のレイアウトを作成してみましょう。
上下の『:』が綺麗に揃うようにレイアウトしていきます。

スクリーンショット 2020-05-11 19.00.16 (1)

まずは綺麗なレイアウトを考える前に、文字を配置していきましょう。

上の行からみていきます。『ID』『:』『毎回同じ〜』これら3つのTextが横に並んでいます。複数のWidgetを横並びで表示するにはRow Widgetを使います。

Row Widgetとは
children Propertyに入っている複数のWidgetを横並びに表示してくれるWidget。
縦表示に使うのはColumn Widget。

下の行も同様に3つのTextが横に並んでいるので、Rowを用いて配置していきます。
そして、これら2つのRowが縦に並んでいるので、Columnにてまとめて縦に表示します。

スクリーンショット 2020-05-11 19.19.38

ここまでをまとめるとこの様になります。

body:  Column(
   children: <Widget>[
     Row(
       children: <Widget>[
         Text('ID'),
         Text(':'),
         Text('毎回同じテキストを表示ID'),
       ]
     ),
     Row(
       children: <Widget>[
         Text('PW'),
         Text(':'),
         Text('毎回同じテキストを表示PW'),
       ]
     ),
   ],
 ),

次に『:』が上下揃った綺麗なレイアウトを作っていきます。
そもそも綺麗に揃わない理由は、文字が持つ幅に違いがあるからです。
この幅を統一する為に、Container Widgetを用います。

Container Widgetとは
childに含まれたWidgetの幅・高さを設定することができるWidget。

Containerを使いそれぞれのRowの1つめと2つめのTextの幅を設定してみましょう。

スクリーンショット 2020-05-11 19.38.07


body:  Column(
   children: <Widget>[
     Row(
       children: <Widget>[
         Container(
           width: 50,//幅を指定
           child: Text('ID'),
         ),
         Container(
           width: 20,
           child: Text(':'),
         ),
         Text('毎回同じテキストを表示ID'),
       ]
     ),
     Row(
       children: <Widget>[
         Container(
           width: 50,
           child: Text('PW'),
         ),
         Container(
           width: 20,
           child: Text(':'),
         ),
         Text('毎回同じテキストを表示PW'),
       ]
     ),
   ],
 ),

ここまでくればもう少しです。目標のレイアウトを確認すると1行目と2行目の間に少し余白があります。この空間を作り出すことができるのがPadding Widgetです。

Padding Widgetとは

padding Propertyによって、任意の余白を指定した範囲に作り出すことができるWidget。
EdgeInsets.〜にて範囲を指定することができます。

余白ができているのは1行目と2行目の間、つまり2つのRowの間になります。
よって下記の様に記述します。

スクリーンショット 2020-05-11 19.48.09


body:  Column(
   children: [
     Row(
       children: [
         Container(
           width: 50,
           child: Text('ID'),
         ),
         Container(
           width: 20,
           child: Text(':'),
         ),
         Text('毎回同じテキストを表示ID'),
       ]
     ),
     Padding(padding: EdgeInsets.all(5.0),),//上下左右に5.0の余白を作る
     Row(
       children: [
         Container(
           width: 50,
           child: Text('PW'),
         ),
         Container(
           width: 20,
           child: Text(':'),
         ),
         Text('毎回同じテキストを表示PW'),
       ]
     ),
   ],
 ),

最後に今は全体が左上に寄っているので、全体にもPaddingを適用して、目標のレイアウトにしてみましょう。

スクリーンショット 2020-05-11 23.17.05


body: Padding(
 padding: EdgeInsets.all(30.0),//bodyに含まれるWidget全体に余白を追加
 child: Column(
   children: [
     Row(
       children: [
         Container(
           width: 50,
           child: Text('ID'),
         ),
         Container(
           width: 20,
           child: Text(':'),
         ),
         Text('毎回同じテキストを表示ID'),
       ]
     ),
     Padding(padding: EdgeInsets.all(5.0),),
     Row(
       children: [
         Container(
           width: 50,
           child: Text('PW'),
         ),
         Container(
           width: 20,
           child: Text(':'),
         ),
         Text('毎回同じテキストを表示PW'),
       ]
     ),
   ],
 ),
),

遷移後の画面のレイアウトも綺麗に整いました。
次でいよいよスターターコース最後の内容となります。

【10】新しい画面にリストの情報を引き継ぐ

リストの情報を新しい画面に引き継ぎ、表示しましょう。
タイトルにリストのタイトルを表示。IDとPWは任意のもので結構です。

スクリーンショット 2020-05-11 23.17.05

こちらはNavigator.pushにてNextPageに遷移する際に、titleList[i]の値を引数として送ることで実現します。

まずは、遷移後のNextPageにて引数を受け取る準備をします。
NextPageクラスに引数の受け皿となる変数を定義します。

Class NextPage extends StatefulWidget {
 final String title;
 //titleという変数を宣言。titleListの値はStringなので型宣言も忘れずに。
 
 @override
 _NextPageState createState() => _NextPage();
}

次に、引数として送られてきた値を今定義したtitleという変数に渡す様に記述を加えます。

Class NextPage extends StatefulWidget {
 final String title;
 NextPage(this.title);//送られてきた値をtitleに渡す。
 
 @override
 _NextPageState createState() => _NextPage();
}

すると、material.dartの遷移に関する記述に下記のエラーが発生していると思います。

スクリーンショット 2020-05-11 23.48.26

これは先ほど記述したNextPage(this.title);によって発生しているものです。

変数titleにStringを送りたいから遷移するなら、何かStringを渡してくださね。と言っているにも関わらず、何も値を送っていない為発生しているエラーです。

引数の渡し方は簡単で、NextPage()の()の中に送りたい値を記述するだけです。
これを記述すると、この様になります。

Navigator.push(context, 
MaterialPageRoute(builder: (context) => NextPage(titleList[index])));

これによってtitleList[index]がNextPage Classにて定義した変数titleに送られます。

あとは、next_page.dartのAppBar Widgetのtitle Propertyにこの変数titleを用いて記述できれば完了です。

ここではあまり深く触れませんが、Class NextPageにて定義された変数を、Class _NextPageStateにて使うには、変数名の前に『widget.』をつける必要があります。

これを踏まえて記述をするとこの様になります。

Widget build(BuildContext context) {
 return Scaffold(
   appBar: AppBar(
     title: Text(widget.title),
   ),
 ),
}

全てのリストでちゃんとタイトルが引き継がれていることを確認してみましょう。
引き継ぎができている様でしたら、スターターは修了です。

【11】まとめ

〜こちらの教材で得たスキル〜

Flutterを使用できる環境の構築
基本的なDartの記述方、Flutterの使用方法の理解
画面遷移などの基本的なアプリ構成の理解

画像23

おめでとうございます!

これにてスターターは修了です。スタンダードコースをこのまま受講される方は、次のビギナーにて教材(3つのアプリ)を一つ選択して頂きます。
そこではより実用的なアプリの作成方法を学んで頂くことが可能です。

もし興味がありましたら、是非スタンダードコースも受講してみてください。

ハッチアウトスクール ハッチアウトスクールの公式サイトです hatchout.net