こちらを参照 docs.flutter.dev
CPUアーキテクチャごとにapkを分けて生成
flutter build apk --split-per-abi
全環境まとめて
flutter build apk
こちらを参照 docs.flutter.dev
CPUアーキテクチャごとにapkを分けて生成
flutter build apk --split-per-abi
全環境まとめて
flutter build apk
Riverpodが提供するProviderは、値をキャッシュしてbuildのたびに処理が動かないようにしてくれている。 FutureProviderでWebAPIをコールする処理がbuildの度に実行されないのは便利だ。
しかし、キャッシュを使いたくないと思っていたのに使われていたということも起こりがちなので、どういう場合にキャッシュが使われるのかを把握しておくことは重要だ。
今回は、下記のパターンでキャッシュが使われるかどうかを確認した。
WidgetにてそれぞれのProviderをref.watchして、下記の状況でキャッシュが返却されるかどうかを確認する。
Riverpodの仕様で、下記の場合は再計算を行うから。
autoDisposeを指定している場合、画面がdisposeされProviderをwatchするWidgetがなくなると、Providerは破棄される。 その後、画面を再度生成すると、パターンaと同様の状況になり値が新規に計算される。
一方、1~4のProvoderは画面の破棄だけでは影響をうけないので、更新や破棄などが発生せずキャッシュが返却される。(画面の破棄によって影響を受けるように作れば当然再計算されます。)
例えば、ポップアップ画面を表示する際に最新のデータをProviderで取得して表示する。という機能があるとする。
この場合、一度画面を閉じて再度開いた時には最新の値が表示されることが期待される(キャッシュをだしてほしくない)ので、autoDisposeを指定しておくことが望ましい。
そすれば、画面が破棄されるのと同時にProviderは破棄され、再度画面を開いた際にProviderが再び値を再計算してくれる。
現在時刻を返却する、引数のないWebAPIがあるとする。このAPIにFutureProviderでアクセスして値を取得し、画面表示する機能を考える。
今回の結果から言うと、このFutureProviderはリビルドを行っても毎回キャッシュを返却してしまう。
ビルドのたびに常に最新の値を取得したいというニーズがある場合は、FutureProviderの内部にref.watchを配置するなどの工夫を行う必要がある。
//app.js (This is a target of testing.) const moduleA = require("./moduleA.js")(); module.exports = (()=>{ console.log(moduleA.showValue()); moduleA.add(10); console.log(moduleA.showValue()); })();
//moduleA.js (External library like a web API and database module) const moduleA = require("./moduleA.js")(); module.exports = (()=>{ console.log(moduleA.showValue()); moduleA.add(10); console.log(moduleA.showValue()); })();
//app.test.js (BAD CASE) const moduleA = require('../moduleA.js')(); jest.mock('../moduleA.js', () => () => { return { add: jest.fn(), showValue: jest.fn().mockImplementation(() => 1000) }; }); describe("app", () => { test("", () => { require("../app.js"); expect(moduleA.add).toHaveBeenCalled(); //----->Failed. jest says add has never been called. expect(moduleA.showValue).toHaveBeenCalled(); }); });
//app.test.js (GOOD CASE) const moduleA = require('../moduleA.js')(); jest.mock('../moduleA.js', () => () => { this.instance = this.instance || {//---> This mock should be singleton. add: jest.fn(), showValue: jest.fn().mockImplementation(() => 1000) }; return this.instance; }); describe("app", () => { test("", () => { require("../app.js"); expect(moduleA.add).toHaveBeenCalled(); //-----> Pass! expect(moduleA.showValue).toHaveBeenCalled();//-----> Pass! }); });
Here is all source code.
github.com
とあるLambda関数Aから別のLambda関数Bを呼び出して、
関数Bの処理が終わったら関数Aの処理にもどるような仕組みをつくりたい。
Lambda関数 test_method_A
この関数には、事前に「lambda:InvokeFunction」の権限を与えておくこと。
import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda"; export const handler = async (event) => { const client = new LambdaClient(); const input = { FunctionName: "test_method_B" }; const command = new InvokeCommand(input); const res = await client.send(command); //Uint8Arrayで返却されるためデコードする。 const decoder = new TextDecoder(); const text = decoder.decode(res.Payload); console.log(text); const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; return response; };
Lambda関数 test_method_B
export const handler = async(event) => { const response = { statusCode: 200, body: JSON.stringify('I\'m method B!'), }; return response; };
test_method_Bのデータを取得できた。
INFO {"statusCode":200,"body":"\"I'm method B!\""}
Flutterアプリ側でボタンを押すと、Watch OSアプリ側のカウンターが増加するアプリを作成します。
ソースコードはこちら。
github.com
<----------------------宣伝------------------------->
Flutterアプリ制作・バグ修正承ります!
coconala.com <----------------------宣伝------------------------->
Xcode->Window->Devices and Simulatorsを選択し、左下の+ボタンをクリックしてシミュレーターの新規作成ダイアログを表示する。
上記のように設定してNext
ペア設定するウォッチの設定をしてCreateする。
flutter pub add flutter_watch_os_connectivity
を実行してflutter_watch_os_connectivityをプロジェクトに追加する。
下記のコードを入力してビルドする。(Build->Flutter->Build iOS)
import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_watch_os_connectivity/flutter_watch_os_connectivity.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int counter = 0; final FlutterWatchOsConnectivity _flutterWatchOsConnectivity = FlutterWatchOsConnectivity(); void incrementCounter() { counter++; sendMessage("$counter"); } Future<void> sendMessage(String txt) async { bool isReachable = await _flutterWatchOsConnectivity.getReachability(); if (isReachable) { await _flutterWatchOsConnectivity.sendMessage({"COUNTER": txt}); } else { if (kDebugMode) { print("No reachable watches."); } } } @override void initState() { super.initState(); _flutterWatchOsConnectivity.configureAndActivateSession(); _flutterWatchOsConnectivity.activationStateChanged .listen((activationState) { if (activationState == ActivationState.activated) { if (kDebugMode) { print("activationDidCompleteWith state= ${activationState.name}"); } } }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: const <Widget>[ Text( 'Sample App', ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () { incrementCounter(); }, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
ビルドは失敗するが、iOS用のコードは生成されているはずなので次へ進む。
Xcodeを開きFlutterから生成されたiOSのプロジェクトを開く。
Flutterプロジェクト->ios->Runner.xcworkspace
Xcode上でFile->New->Targetとしてダイアログを開き、watchOSタブのAppを選択してnext
任意のProduct Nameを入力して、Watch App for Existing iOS Appを選択してfinish
新しくディレクトリが作成されるので、ContentViewファィルにウォッチ側のコードを記入する。
// // ContentView.swift // import SwiftUI import WatchConnectivity import AVFoundation struct ContentView: View { @ObservedObject var connector = PhoneConnector() var body: some View { VStack { VStack { Text(String(connector.counter)) .font(.largeTitle) .foregroundColor(Color.gray) } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } class PhoneConnector: NSObject, ObservableObject, WCSessionDelegate { @Published var counter = 0 override init() { super.init() if WCSession.isSupported() { WCSession.default.delegate = self WCSession.default.activate() } } func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { print("activationDidCompleteWith state= \(activationState.rawValue)") } func session(_ session: WCSession, didReceiveMessage message: [String : Any]) { print("didReceiveMessage: \(message)") //受け取ったメッセージから解析結果を取り出す。 let result = message["COUNTER"] as! String if let c = Int(result) { //結果を画面に反映 debugPrint(c) DispatchQueue.main.async { self.counter = c } } } }
ファイルツリーのRunnerをダブルクリックして設定を表示する。
左側のツリーでRunnerを選択した状態でGeneralのFrameworks,Libraries and Embedded Contentの追加ボタンをクリック
作成済みのウォッチアプリが表示されるので選択してAdd
続いてCounting Watch Appを選択し、InfoタブのWKCompanionBundleIdentifierにRunnerのBundleIdentierを入力する。
RunnerのBundleIdentierは、Runner->General->Identifierから確認できる。
そのままSigning & Capabilitiesタブへ移動し、Bundle Identifierを修正する。
ターゲットにWatchCountingSampleを選択する。
実行環境に先ほど作成したシミュレーターを指定する。
ビルドが成功すれば、シミュレーターが起動する。
その後、ターゲットにRunnerを指定し同じシミュレーターを指定してこちらも実行すると、こちらのように両方のアプリが起動する。
シングルページアプリケーションは下図のような構成をとることが多い。
この構成の場合は、各サーバーの役割は、こうなる。
静的なファイル置き場
データを保持する。
本稿では、各サーバー間をデータがどのようにながれていくのかを図で説明する。
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.New() r.GET("/book", func(c *gin.Context) { //パラメータの確認 (2-②の処理) //データベースへのクエリ発行と結果取得 (2-③、3-①の処理) //クエリ結果をjsonデータへ加工してレスポンス作成 (3-②の処理) }) r.Run() }
function onSearchButtonClicked(keyword) { const response = await fetch("https://aaa.com/book?n=" + keyword);(2-①、3-③の処理) //結果を加工して画面表示 (3-③の処理) }
Flutterでアプリを作成する際、アプリが持つべき機能や構造を何度も実装するのはとても無駄なことですね。
AndroidStudioでFlutterプロジェクトを作成すると、カウンターアプリが生成されますが、欲しいコードが全然実装されてません。
そこで、よく使うコードを含んだテンプレートを作成しました。
カウンターの値と、最終更新日をローカルに保持するアプリです。
SharedPreerenceでローカルにデータを保存する機能
データベースに置き換えて使ってもよき
多言語化の予定が無くても、文字列リソース置き場として使った方がいいと思う
LayoutBuilderとbreakpointで、スマホ、タブレット、デスクトップに場合分け
ダークモードや言語設定などのアプリ全体へ反映したい設定を行うクラスあり
Androidアプリ開発で私が良く使っている構造です。
同じモバイルアプリなので、当然Flutterにも適用可能です。
Widgetの集まりです。
なるべく状態はViewModelにもたせて、StatelessWidgetで書けるとよいと思います。
状態はProviderを使って取得します。
UIの状態を保持し、変更をViewに通知します。
また、複雑なロジックを処理するためにUseCaseをコールしたり、Repositoryを通じてデータの読み書きを行います。
複雑なロジックを実行します。
シンプルな場合は、クラスを作らずにViewModelに書いたりもします。
データの読み書きを行います。
データの読み書きに関しては様々なライブラリがあると思いますが、Repositoryより下位に隠蔽しておくことで、ライブラリを変更したときの影響範囲を小さくできます。
実際にデータへのアクセスを実装しています。