カテゴリー
Flutter

FlutterAppとWebViewで交信してみた

        function executeJavascript() {
            return "Execute JavaScript."
        }
        function sendFlutterApp() {
            flutterJavaScript.postMessage("Post message from JavaScript.");
        }
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Javascript Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Javascript Demo Home Page'),
    );
  }
}

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> {

  late WebViewController _controller;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: WebView(
        onWebViewCreated: (WebViewController webViewController) async {
          _controller = webViewController; // 生成されたWebViewController情報を取得する
        },
        initialUrl: 'https://mymyser.com/flutterjs.html',
        javascriptMode: JavascriptMode.unrestricted,
        javascriptChannels: {
          JavascriptChannel(
            name: 'flutterJavaScript',
            onMessageReceived: (result) async {
              print(result.message);
            },
          )
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _executeJavascript,
      ),

    );
  }

  Future<void> _executeJavascript() async {
    String result = await _controller.runJavascriptReturningResult('executeJavascript()');
    print(result);
  }
}
カテゴリー
Flutter

Ryzen+Ubuntu+FlutterでAndroidエミュレーターを動かす

 2021/08頃にFlutterの環境をUbuntuで作ったときRyzenのCPUではAndroidエミュレーターが動きませんでした。

 Android Studio – Arctic Fox | 2020.3.1 Patch 3にしたらRyzenでもエミュレーターが動くようになった。

カテゴリー
Flutter

Flutterで状態管理あれこれ2

 状態管理の元となるInheritedWidgetを理解するためにカウンターアプリを改造して実装してみましたがとても複雑でした。

 それで流行りのパッケージを実装してみました。使いやすいように InheritedWidget をラップしているのでコードは短くなるのですが、バージョンによって細かく記述方法が異なりなり正解にたどり着くのが大変です。

  flutter_hooks: ^0.17.0
  hooks_riverpod: ^0.14.0+4
  state_notifier: ^0.7.0
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

final viewModel = StateNotifierProvider((_) => ViewModel());

class ViewModel extends StateNotifier<int> {
  ViewModel() : super(0);
  void increment() => state++;
  void decrement() => state--;
}

void main() {
  runApp(
    ProviderScope(
      child: CounterApp(),
    ),
  );
}

class CounterApp extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final _viewModel = useProvider(viewModel);

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('CounterApp')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have pushed the button this many times:',
              ),
              Text(
                _viewModel.toString(),
                style: Theme.of(context).textTheme.headline4,
              ),
            ],
          ),
        ),
        floatingActionButton: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton(
              heroTag: 'increment',
              onPressed: () => context.read(viewModel.notifier).increment(),
              child: Icon(Icons.add),
            ),
            Gap(15),
            FloatingActionButton(
              heroTag: 'decrement',
              onPressed: () => context.read(viewModel.notifier).decrement(),
              child: Icon(Icons.remove_circle_outline),
            ),
          ],
        ),
      ),
    );
  }
}

 このコードもパッケージのバージョンがちょっとでも上がれば直さなくなると思います。

Flutterで状態管理あれこれ2
Flutterで状態管理あれこれ2

実機のスクリーンショットは
flutter screenshot
で撮れます。プロジェクトフォルダーに画像が保存される。

カテゴリー
Filter

Flutterで角を丸くする

 TextFieldやDropdownButtonの角を丸くしてみた。結局ConainerのBorderを丸くして角が丸くなったように見せるだけだった。

Flutterで角を丸くする
Flutterで角を丸くする
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    String dropdownValue = 'One';
    return Scaffold(
      backgroundColor: Colors.grey,
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ClipRRect(
              borderRadius: BorderRadius.all(Radius.circular(30)),
              child: Container(
                width: 200,
                height: 100,
                color: Colors.yellow,
                child: Padding(
                  padding: const EdgeInsets.all(8),
                  child: const TextField(
                    obscureText: true,
                    decoration: InputDecoration(
                      border: InputBorder.none,
                      labelText: 'Pass',
                    ),
                  ),
                ),
              ),
            ),
            Gap(20),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 5),
              width: 200,
              decoration: BoxDecoration(
                border: Border.all(color: Colors.blue),
                borderRadius: BorderRadius.circular(20),
              ),
              child: const TextField(
                obscureText: true,
                decoration: InputDecoration(
                  border: InputBorder.none,
                  labelText: 'Pass',
                ),
              ),
            ),
            Gap(20),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 5),
              width: 200,
              decoration: BoxDecoration(
                border: Border.all(color: Colors.blue),
                borderRadius: BorderRadius.circular(20),
              ),
              child: DropdownButton<String>(
                value: dropdownValue,
                icon: const Icon(Icons.arrow_downward),
                iconSize: 24,
                elevation: 16,
                style: const TextStyle(color: Colors.deepPurple),
                onChanged: (String? newValue) {},
                items: <String>['One', 'Two', 'Free', 'Four']
                    .map<DropdownMenuItem<String>>((String value) {
                  return DropdownMenuItem<String>(
                    value: value,
                    child: Text(value),
                  );
                }).toList(),
              ),
            ),
            Gap(20),
            Text(
              'You have pushed the button this many times:',
              style: TextStyle(color: Colors.white),
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
カテゴリー
Flutter

Flutterの状態管理あれこれ

 状態管理の要点はその状態に影響を受けるWidgetのみが状態変化に対する処理を行えるように効率化することです。

InheritedWidget
 Flutter自身を構成するWidgetの一つ。Widgetツリーの配下に効率的に情報を伝える仕組みを提供します。

Flutter_InheritedWidget
Flutter_InheritedWidget
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(
        key: null,
        child: Scaffold(
          appBar: AppBar(
            title: Text("Inherited Widget Sample"),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                WidgetNumText(),
                WidgetCenterText(
                  key: null,
                ),
              ],
            ),
          ),
          floatingActionButton: WidgetIncrementBtn(),
        ),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({required Key? key, required this.child}) : super(key: key);

  final Widget child;

  @override
  _MyHomePageState createState() => _MyHomePageState();

  static _MyHomePageState of(BuildContext context, {bool rebuild = true}) {
    return rebuild
        ? (context.dependOnInheritedWidgetOfExactType<_InheritedWidget>()
                as _InheritedWidget)
            .data
        : (context
                .getElementForInheritedWidgetOfExactType<_InheritedWidget>()!
                .widget as _InheritedWidget)
            .data;
  }
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return _InheritedWidget(
      child: widget.child,
      data: this,
    );
  }
}

class WidgetIncrementButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final _MyHomePageState state = MyHomePage.of(context, rebuild: false);
    return FloatingActionButton(
      onPressed: () => state._incrementCounter(),
      tooltip: 'Increment',
      child: Icon(Icons.add),
    );
  }
}

class _InheritedWidget extends InheritedWidget {
  _InheritedWidget({
    Key? key,
    required Widget child,
    required this.data,
  }) : super(key: key, child: child);

  final _MyHomePageState data;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => true;
}

class WidgetNumText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final _MyHomePageState state = MyHomePage.of(context);
    return Text(
      '${state._counter}',
      style: Theme.of(context).textTheme.headline4,
    );
  }
}

class WidgetIncrementBtn extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final _MyHomePageState state = MyHomePage.of(context, rebuild: false);
    return FloatingActionButton(
      onPressed: () => state._incrementCounter(),
      tooltip: 'Increment',
      child: Icon(Icons.add),
    );
  }
}

class WidgetCenterText extends StatelessWidget {
  const WidgetCenterText({required Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Text('You have pushed the button this many times:');
  }
}

Memo
Shader compilation errorが出たら
エミュレーターからアプリを消してflutter cleanする


Provider
 InheritedWidgetのラッパークラスで状態管理とDI機能を提供します。

StateNotifer
状態管理パッケージでProviderをベースに作られている。同じ作者。