flutterでテストアプリを作ってみる
自分用の備忘録
とりあえずプロジェクト作成
今回はWindows(SurfaceGo2)で試す。
SDカード側に入れたからか、平行でWindowsアップデート中だからか分からないが遅い・・・
一応AndroidStudioも入れたが、こっちも環境作成に時間がかかるので、まずはWeb版からがいいかも。
最終的にはスマホ機能(電話関係とか)を試した。
createでプロジェクト作成
flutter create test
で出来たものを利用する。
VSCodeのプラグイン
公式っぽいやつは入れておいた。
他にもあるらしい。
https://zenn.dev/hagakun_dev/articles/2f2eb65b892bea
サンプル作成
- とりあえずトップ画面にアイコンを配置して、別の画面を開くようにしてみる
- アイコンから適当なURLに飛ぶ(ブラウザが開く)
- アイコンから電話アプリが起動する
- アイコンからローカルのファイルを開く
くらいが出来るようにしてみたい。
見た目はこんな感じにとりあえず作った。
IconButtonというウィジェットがあるので、アイコンはこれを配置する。
アイコンそのものにはアイコンウィジェットというものを利用するのが楽。
手元の画像を使う方法もある。
https://qiita.com/ikemura23/items/5671bba76136d940b618とか
https://zerokara-app.com/930/
レイアウトはhttps://future-architect.github.io/articles/20210513b/
をまずは参考に。
基本的にはヘッダ、ボディ、フッタのエリアがあって、ボディ部分にrow,columnでテーブルみたいに並べる、
というようにする。
https://www.wenyanet.com/opensource/ja/5fedcb7d9c1222717e361102.htmlのようにフレキシブルなグリッドレイアウトもあるっぽい。
パッケージのインストール
インストールは書くモジュールのページに説明があるので略
引数付きでonPressedを実行する
何も考えずに書くと
Error: This expression has type 'void' and can't be used
になった。
Ink(
color: Colors.blue,
child:IconButton(
icon: Icon(Icons.android),
onPressed: () => _linkPage(
Uri(
scheme:'https',
host:'www.city.kagoshima.lg.jp',
path:'/',
queryParameters: {"lang":"ja"},
)
),
)
)
こんな感じで() => function(x,y)
とする。
一旦作成
とりあえず基本機能を盛って試してみたmain
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:test/page1.dart';
import 'dart:io';
import 'dart:convert';
import 'package:path_provider/path_provider.dart';
import 'package:http/http.dart' as http;
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: 'sapmle',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'MainPage'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// JSON分解のテスト用
static JsonMap parseJsonMap(String input) => json.decode(input) as JsonMap;
// ネットから取得して保存されるテキストデータの代わりに固定文字のJSONで試す
JsonMap psjson = parseJsonMap('{ "urls":[{"icon":"1.png","url":"www.google.com"},{"icon":"2.png","url":"www.yahoo.co.jp"},{"icon":"3.png","url":"www.excite.co.jp/"},{"icon":"4.png","url":"www.pref.kagoshima.jp/"}]}');
void _changePage() {
setState(() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Page1(title: 'SubPage'))
);
});
}
// PDFファイルのダウンロードテスト
void _pdfDownload() async {
String filePath = (await getApplicationDocumentsDirectory()).path;
var _httpClient = http.Client();
final http.Response res = await _httpClient.get(Uri.parse("https://www.pref.kyoto.jp/kamogawa/documents/1200618028055.pdf"));
final file = File('${filePath}/sample.pdf');
await file.create();
await file.writeAsBytes(res.bodyBytes);
}
// 外部ブラウザで開く
void _linkPage(uri) async {
try{
setState(() async {
await launchUrl(
uri,
mode: LaunchMode.externalApplication,
);
});
}
catch(e){
throw 'Could not Launch $uri';
}
}
// アプリ起動時に一度実行するメソッド。非同期を待つことは出来ない模様
@override
void initState(){
_pdfDownload();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
// 固定配置分
Container(
height:150,
child: GridView.extent(
maxCrossAxisExtent: 150,
padding: const EdgeInsets.all(4),
mainAxisSpacing: 4,
crossAxisSpacing: 4,
children: <Widget>[
Ink(
height: 150,
width: 150,
color: Colors.blue,
child:IconButton(
icon: const Icon(Icons.pages),
onPressed: _changePage,
)
),
Ink(
height: 150,
width: 150,
color: Colors.orange,
child:IconButton(
icon: const Icon(Icons.link),
onPressed: () => _linkPage(
Uri(
scheme:'https',
host:'www.city.kagoshima.lg.jp',
path:'/',
queryParameters: {"lang":"ja"},
)
),
)
),
Ink(
height: 150,
width: 150,
color: Colors.pink,
child:IconButton(
icon: const Icon(Icons.phone),
onPressed: () => _linkPage(
Uri.parse('tel:0120444444')
),
)
)
],
)
),
// Mapから動的に作成する分
Expanded(
child: GridView.extent(
maxCrossAxisExtent: 150,
padding: const EdgeInsets.all(4),
mainAxisSpacing: 4,
crossAxisSpacing: 4,
children: psjson["urls"].map((e) => Ink(
height: 150,
width: 150,
color: Colors.amber,
child:TextButton(
child: Text(e["icon"]),
onPressed: () => _linkPage(
Uri(
scheme:'https',
host:e["url"],
path:'/',
queryParameters: {"lang":"ja"},
)
),
)
)
).toList().cast<Widget>(),
),
)
],
),
),
);
}
}
あと、PDFファイルをShareする場合はこんな感じ
void _pdfOpen(filename) async {
setState(() async{
String filePath = (await getApplicationDocumentsDirectory()).path;
await Share.shareFiles(['${filePath}/${filename}']);
});
}
onPressed: () => _pdfOpen("sample.pdf")
正解なのかは分からないが、とりあえず動いた。
### 注意点
iOSのシミュレーターでは電話アプリが起動しないだとか、実機でないと出来ない事も
ちょいちょいあるので注意が必要。(色々時間を浪費した)
画面を非同期で対応する
さて、ここでインターネットからダウンロードしたJSONファイルからメニューを作成したい
というような要望がある場合に先のコードでは上手くいかない。
Dartのasync付きの関数はFuture<String>
のような、非同期だけど後からString型を返すよ、
みたいな感じの型を設定してておく必要がある。
この場合は先のようなWigetの書き方ではエラーになってしまう。
initState()
で実行させてList型を作る用にしておいたところで、
非同期の完了をこのメソッドは待ってくれないので非同期処理が終わる前に
画面表示が走ってしまうと上手く表示してくれない・・・
という時に役に立つのが
FutureBuilderかStreamBuilder、らしい。
https://gakogako.com/fluter_futurebuilder/
これは次回試してみておきたい。。。