Flutterは、AndroidとiOSの両方に対応したモバイルアプリを作成するためのフレームワークです。実際には、デスクトップアプリだけでなく、ウェブアプリの作成にも使用することができます。成熟したフレームワークで、サポートも充実しており、現在のトレンドになっています。

一方、MySQLは、主にウェブやモバイルアプリのデータをホストするために使用されるデータベースです。フリーで、おそらく市場で最も人気のあるRDBMSフレームワークです。一般的には、サーバーサイドのプログラミング言語であるPHPと組み合わせて使用します。PHPとMySQLの組み合わせは、ウェブの大部分を支えています。

このレッスンでは、MySQLデータベースとflutterアプリケーションの両方を操作する方法を見てみましょう。PHPのMySQLサーバーに対してHTTP GETを実行し、データをダウンロードして、カスタムリストビューに画像やテキストを入力します。そして、リストビューの中のカードが1枚クリックされると、mysqlからダウンロードしたデータを渡して詳細ページを開きます。

デモ

このプロジェクトで作成されたデモを紹介します。


ビデオチュートリアル

ここでは、ビデオチュートリアルを紹介します。

1. PHP

まず、以下のようなPHPコードを書く必要があります。

    1. mysqli クラスを使用して mysql データベースに接続します。
  1. mysqlデータベースのテーブルからすべてのレコードを選択する。
  2. 結果をPHPの配列に返します。
  3. その配列をJSONエンコードし、呼び出し元にプリントします。
(a). index.php

これが私たちの唯一のファイルです。オブジェクト指向のPHPコードを書きます。このファイルの最初のステップは、データベースの認証情報を格納するConstantsクラスを作成することです。データベースをMySQLで管理しているので、データベース名、ユーザー名、パスワードなどの認証情報を定義する必要があります。

index.phpファイルに、冒頭のPHPタグを記述します。

<?php

次に、静的なデータベース認証情報を持つクラスを作成します。

class Constants
{
    //DATABASE DETAILS
    static $DB_SERVER="localhost";
    static $DB_NAME="spacecraftsDB";
    static $USERNAME="root";
    static $PASSWORD="";

次に、データベースからすべてのデータを選択するためのSQLステートメントを定義します。

    static $SQL_SELECT_ALL="SELECT * FROM spacecraftsTB";

}

同じphpファイル内で、CRUDメソッドをホストする別のクラスを作成します。

class Spacecrafts
{

このクラスでは、mysqliを使ってデータベースに接続するための関数を定義します。mysqliクラスのコンストラクタには、データベースサーバ、データベース名、ユーザ名、パスワードを渡します。そして、接続がうまくいかなかった場合はnullを返し、そうでない場合はmysqli`のインスタンスを返します。

    public function connect()
    {
        $con=new mysqli(Constants::$DB_SERVER,Constants::$USERNAME,Constants::$PASSWORD,Constants::$DB_NAME);
        if($con->connect_error)
        {
            // echo "Unable To Connect"; - For debug
            return null;
        }else
        {
            //echo "Connected"; - For debug
            return $con;
        }
    }

次の関数では、mysqlデータベースからデータを選択することができます。データベースからデータを取得して、そのデータを配列にプッシュし、その配列をエンコードしてプリントします。

    public function select()
    {
        $con=$this->connect();
        if($con != null)
        {
            $result=$con->query(Constants::$SQL_SELECT_ALL);
            if($result->num_rows>0)
            {
                $spacecrafts=array();
                while($row=$result->fetch_array())
                {
                    array_push($spacecrafts, array("id"=>$row['id'],"name"=>$row['name'],
                    "propellant"=>$row['propellant'],"destination"=>$row['destination'],
                    "image_url"=>$row['image_url'],"technology_exists"=>$row['technology_exists']));
                }
                print(json_encode(array_reverse($spacecrafts)));
            }else
            {
                print(json_encode(array("PHP EXCEPTION : CAN'T RETRIEVE FROM MYSQL. ")));
            }
            $con->close();

        }else{
            print(json_encode(array("PHP EXCEPTION : CAN'T CONNECT TO MYSQL. NULL CONNECTION.")));
        }
    }

以下は index.php の完全なコードです。

<?php

class Constants
{
    //DATABASE DETAILS
    static $DB_SERVER="localhost";
    static $DB_NAME="spacecraftsDB";
    static $USERNAME="root";
    static $PASSWORD="";

    //STATEMENTS
    static $SQL_SELECT_ALL="SELECT * FROM spacecraftsTB";

}

class Spacecrafts
{
    /*******************************************************************************************************************************************/
    /*
       1.CONNECT TO DATABASE.
       2. RETURN CONNECTION OBJECT
    */
    public function connect()
    {
        $con=new mysqli(Constants::$DB_SERVER,Constants::$USERNAME,Constants::$PASSWORD,Constants::$DB_NAME);
        if($con->connect_error)
        {
            // echo "Unable To Connect"; - For debug
            return null;
        }else
        {
            //echo "Connected"; - For debug
            return $con;
        }
    }
    /*******************************************************************************************************************************************/
    /*
       1.SELECT FROM DATABASE.
    */
    public function select()
    {
        $con=$this->connect();
        if($con != null)
        {
            $result=$con->query(Constants::$SQL_SELECT_ALL);
            if($result->num_rows>0)
            {
                $spacecrafts=array();
                while($row=$result->fetch_array())
                {
                    array_push($spacecrafts, array("id"=>$row['id'],"name"=>$row['name'],
                    "propellant"=>$row['propellant'],"destination"=>$row['destination'],
                    "image_url"=>$row['image_url'],"technology_exists"=>$row['technology_exists']));
                }
                print(json_encode(array_reverse($spacecrafts)));
            }else
            {
                print(json_encode(array("PHP EXCEPTION : CAN'T RETRIEVE FROM MYSQL. ")));
            }
            $con->close();

        }else{
            print(json_encode(array("PHP EXCEPTION : CAN'T CONNECT TO MYSQL. NULL CONNECTION.")));
        }
    }
}
$spacecrafts=new Spacecrafts();
$spacecrafts->select();

//end

2. ダーツ

次のパートでは、Dartコードを書きます。Dartは、私たちがFlutterアプリを作成するために使用するプログラミング言語です。ファイルは1つだけです。

(a). main.dart

まず、importを追加します。

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'dart:convert';

宇宙船を表現するために、Spacecraftというクラスを作ります。これは私たちのモデルクラスです。この宇宙船のプロパティをこのクラスのインスタンスとして定義します。このプロパティには id, name, image URL, propellant があります。

class Spacecraft {
  final String id;
  final String name, imageUrl, propellant;

これらのプロパティはコンストラクタで受け取ります。

  Spacecraft({
    this.id,
    this.name,
    this.imageUrl,
    this.propellant,
  });

JSONデータを Spacecraft オブジェクトに変換する役割を持つ関数を用意しています。

  factory Spacecraft.fromJson(Map<String, dynamic> jsonData) {
    return Spacecraft(
      id: jsonData['id'],
      name: jsonData['name'],
      propellant: jsonData['propellant'],
      imageUrl: "http://192.168.12.2/PHP/spacecrafts/images/"+jsonData['image_url'],
    );
  }
}

また、データを一覧表示する画面を作成する必要があります。データをリストアップするには、ListViewを使います。そこで、ListViewアイテムを作成する必要があります。このListViewアイテムがクリックされると、詳細ページを開き、別の画面に結果を表示します。

  Widget createViewItem(Spacecraft spacecraft, BuildContext context) {
    return new ListTile(
        title: new Card(
          elevation: 5.0,
          child: new Container(
            decoration: BoxDecoration(border: Border.all(color: Colors.orange)),
            padding: EdgeInsets.all(20.0),
            margin: EdgeInsets.all(20.0),
            child: Column(
              children: <Widget>[
                Padding(
                  child: Image.network(spacecraft.imageUrl),
                  padding: EdgeInsets.only(bottom: 8.0),
                ),
                Row(children: <Widget>[
                  Padding(
                      child: Text(
                        spacecraft.name,
                        style: new TextStyle(fontWeight: FontWeight.bold),
                        textAlign: TextAlign.right,
                      ),
                      padding: EdgeInsets.all(1.0)),
                  Text(" | "),
                  Padding(
                      child: Text(
                        spacecraft.propellant,
                        style: new TextStyle(fontStyle: FontStyle.italic),
                        textAlign: TextAlign.right,
                      ),
                      padding: EdgeInsets.all(1.0)),
                ]),
              ],
            ),
          ),
        ),
        onTap: () {
          //We start by creating a Page Route.
          //A MaterialPageRoute is a modal route that replaces the entire
          //screen with a platform-adaptive transition.
          var route = new MaterialPageRoute(
            builder: (BuildContext context) =>
            new SecondScreen(value: spacecraft),
          );
          //A Navigator is a widget that manages a set of child widgets with
          //stack discipline.It allows us navigate pages.
          Navigator.of(context).push(route);
        });
  }
}

サーバーからJSONデータをダウンロードする方法に興味があるかもしれません。最初のステップは、Futureオブジェクトを返す関数を定義することです。このメソッドは非同期なので、ユーザーインターフェースのスレッドをブロックすることはありません。この関数の一般的なパラメータは、スペースクラフトのリストです。

Future<List<Spacecraft>> downloadJSON() async {

次に、JSONのエンドポイントを定義します。これは、HTTP GETのリクエストを実行するためのURLです。そして、そのエンドポイントを get() 関数を使って実行します。

  final jsonEndpoint =
      "http://192.168.12.2/PHP/spacecrafts";

  final response = await get(jsonEndpoint);

成功したかどうか、レスポンスコードも確認します。レスポンスステータスコードが200であれば、サーバーとの通信が成功したことになります。そこで、JSONのレスポンスボディをデコードしてリストにします。リストビューでレンダリングするのは、このリストです。

  if (response.statusCode == 200) {
    List spacecrafts = json.decode(response.body);
    return spacecrafts
        .map((spacecraft) => new Spacecraft.fromJson(spacecraft))
        .toList();
  } else
    throw Exception('We were not able to successfully download the json data.');
}

これが全コードです。

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'dart:convert';

class Spacecraft {
  final String id;
  final String name, imageUrl, propellant;

  Spacecraft({
    this.id,
    this.name,
    this.imageUrl,
    this.propellant,
  });

  factory Spacecraft.fromJson(Map<String, dynamic> jsonData) {
    return Spacecraft(
      id: jsonData['id'],
      name: jsonData['name'],
      propellant: jsonData['propellant'],
      imageUrl: "http://192.168.12.2/PHP/spacecrafts/images/"+jsonData['image_url'],
    );
  }
}

class CustomListView extends StatelessWidget {
  final List<Spacecraft> spacecrafts;

  CustomListView(this.spacecrafts);

  Widget build(context) {
    return ListView.builder(
      itemCount: spacecrafts.length,
      itemBuilder: (context, int currentIndex) {
        return createViewItem(spacecrafts[currentIndex], context);
      },
    );
  }

  Widget createViewItem(Spacecraft spacecraft, BuildContext context) {
    return new ListTile(
        title: new Card(
          elevation: 5.0,
          child: new Container(
            decoration: BoxDecoration(border: Border.all(color: Colors.orange)),
            padding: EdgeInsets.all(20.0),
            margin: EdgeInsets.all(20.0),
            child: Column(
              children: <Widget>[
                Padding(
                  child: Image.network(spacecraft.imageUrl),
                  padding: EdgeInsets.only(bottom: 8.0),
                ),
                Row(children: <Widget>[
                  Padding(
                      child: Text(
                        spacecraft.name,
                        style: new TextStyle(fontWeight: FontWeight.bold),
                        textAlign: TextAlign.right,
                      ),
                      padding: EdgeInsets.all(1.0)),
                  Text(" | "),
                  Padding(
                      child: Text(
                        spacecraft.propellant,
                        style: new TextStyle(fontStyle: FontStyle.italic),
                        textAlign: TextAlign.right,
                      ),
                      padding: EdgeInsets.all(1.0)),
                ]),
              ],
            ),
          ),
        ),
        onTap: () {
          //We start by creating a Page Route.
          //A MaterialPageRoute is a modal route that replaces the entire
          //screen with a platform-adaptive transition.
          var route = new MaterialPageRoute(
            builder: (BuildContext context) =>
            new SecondScreen(value: spacecraft),
          );
          //A Navigator is a widget that manages a set of child widgets with
          //stack discipline.It allows us navigate pages.
          Navigator.of(context).push(route);
        });
  }
}

//Future is n object representing a delayed computation.
Future<List<Spacecraft>> downloadJSON() async {
  final jsonEndpoint =
      "http://192.168.12.2/PHP/spacecrafts";

  final response = await get(jsonEndpoint);

  if (response.statusCode == 200) {
    List spacecrafts = json.decode(response.body);
    return spacecrafts
        .map((spacecraft) => new Spacecraft.fromJson(spacecraft))
        .toList();
  } else
    throw Exception('We were not able to successfully download the json data.');
}

class SecondScreen extends StatefulWidget {
  final Spacecraft value;

  SecondScreen({Key key, this.value}) : super(key: key);

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

class _SecondScreenState extends State<SecondScreen> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: new Text('Detail Page')),
      body: new Container(
        child: new Center(
          child: Column(
            children: <Widget>[
              Padding(
                child: new Text(
                  'SPACECRAFT DETAILS',
                  style: new TextStyle(fontWeight: FontWeight.bold,fontSize: 20.0),
                  textAlign: TextAlign.center,
                ),
                padding: EdgeInsets.only(bottom: 20.0),
              ),
              Padding(
                //widget is the current configuration. A State object's configuration
                //is the corresponding StatefulWidget instance.
                child: Image.network('${widget.value.imageUrl}'),
                padding: EdgeInsets.all(12.0),
              ),
              Padding(
                child: new Text(
                  'NAME : ${widget.value.name}',
                  style: new TextStyle(fontWeight: FontWeight.bold),
                  textAlign: TextAlign.left,
                ),
                padding: EdgeInsets.all(20.0),
              ),
              Padding(
                child: new Text(
                  'PROPELLANT : ${widget.value.propellant}',
                  style: new TextStyle(fontWeight: FontWeight.bold),
                  textAlign: TextAlign.left,
                ),
                padding: EdgeInsets.all(20.0),
              )
            ],   ),
        ),
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      theme: new ThemeData(
        primarySwatch: Colors.deepOrange,
      ),
      home: new Scaffold(
        appBar: new AppBar(title: const Text('MySQL Images Text')),
        body: new Center(
          //FutureBuilder is a widget that builds itself based on the latest snapshot
          // of interaction with a Future.
          child: new FutureBuilder<List<Spacecraft>>(
            future: downloadJSON(),
            //we pass a BuildContext and an AsyncSnapshot object which is an
            //Immutable representation of the most recent interaction with
            //an asynchronous computation.
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                List<Spacecraft> spacecrafts = snapshot.data;
                return new CustomListView(spacecrafts);
              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }
              //return  a circular progress indicator.
              return new CircularProgressIndicator();
            },
          ),

        ),
      ),
    );
  }
}

void main() {
  runApp(MyApp());
}
//end
ダウンロード

参考資料です。

No. Location Link
1. GitHub Direct Download
2. GitHub Browse
3. YouTube Video Tutorial
4. YouTube ProgrammingWizards TV Channel
5. Camposha View All Flutter Tutorials

Categorized in: