Home

git switch / git restore コマンドってなんぞ?

October 13, 2024

先日、同僚が git switch ほげほげ とコマンドを打っていて、ん? switch って何?初めて見た、となりました。

端的にいうと git checkout コマンドが持っている次の2つの役割を別のコマンドに分けることで用途を明確にしようというものだそうです。

...

University of London / MSc Computer Science: Project(13〜24 週目)

October 3, 2024

ロンドン大学で MSc Computer Science: Project モジュールを履修中。

講義内容に関して記録した個人的なスタディノートです。

全 24 週のうち 13〜24 週目の内容を記録します。(13 週目開始:2024 年 7 月 8 日 / 12 週目終了:2024 年 10 月 2 日)

...

ドメインレジストラを Cloudflare に変えた

October 2, 2024

自分のブログを検索してみると、3年ほど前にもドメインレジストラを引っ越そうという記事を書いていました。

その時はお名前.com を使っていたらしく、引越し先を検討していた様子。結局お名前.com を使い続けるという終わりになっていたのだけれども、実際にはその後しばらくして Google Domains に引っ越しました。Google Domains は UI が極めてシンプルで使いやすく、また DNS 設定変更後の設定浸透がなぜか非常に早く、大満足していました。(たしか、料金は特に安いということはなかったけれども。)

...

Canonical Multipass で Mac に Ubuntu 環境を作る

September 23, 2024

以前に VMware Fusion Player で Mac に Ubuntu 環境を構築したことがありますが、今回は Canonical Multipass で Ubuntu 環境を作ってみます。

Multipass の公式ウェブサイト #

Multipass のインストール #

以下のページで説明されています。非常にシンプルです。

...

Flutter: ビルドタイムにアサーションを実行する

August 21, 2024

ビルド時にアサーションしたいケース #

次のような Flutter アプリがあったとします。アプリでは myConstant という定数を使用しています。この定数は将来的に変更される可能性がありますが、その場合であっても 16 文字であるという条件は守られなければいけません。

...

Dart: covariant キーワードの使い方

August 19, 2024

covariant キーワードについて #

以下のコードは SubClass の method の部分でエラーになります。エラーメッセージは以下です。

'SubClass.method' ('void Function(ArgumentOfSubClass)') isn't a valid override of 'SuperClass.method' ('void Function(ArgumentOfSuperClass)').
class SuperClassArg {}

class SubClassArg extends SuperClassArg {}

class SuperClass {
  void method(SuperClassArg arg) {
    // Do something...
  }
}

class SubClass extends SuperClass {
  @override
  void method(SubClassArg arg) {
    // Do something...
  }
}

以下のようにすることでエラーは解消します。

...

Dart: コンストラクタの種類が多すぎる

August 18, 2024

公式ドキュメントの案内によると Dart のコンストラクタは6種類あるようです。

Dart implements many types of constructors. Except for default constructors, these functions use the same name as their class.

  • Generative constructors: Creates new instances and initializes instance variables.
  • Default constructors: Used to create a new instance when a constructor hasn’t been specified. It doesn’t take arguments and isn’t named.
  • Named constructors: Clarifies the purpose of a constructor or allows the creation of multiple constructors for the same class.
  • Constant constructors: Creates instances as compile-type constants.
  • Factory constructors: Either creates a new instance of a subtype or returns an existing instance from cache.
  • Redirecting constructor: Forwards calls to another constructor in the same class.

またコンストラクタと同等の動きをするものとして、インスタンスを返す static メソッドを実装することもできます。これらのコンストラクタを見ていきましょう。

...

Flutter: BoxConstraints を理解する

August 17, 2024

Flutter の BoxConstraints について。

期待通りにはいかない表示 #

次のコードでは、画面に何が表示されるでしょうか?

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    home: SizedBox(
      width: 100,
      height: 100,
      child: ColoredBox(
        color: Colors.orange,
      ),
    ),
  ));
}

「一辺 100px」のオレンジ色の四角形」が表示されると思いきや、実際には「画面いっぱいに(つまり画面分の高さと幅の)」オレンジ色が表示されます。

...

Flutter: 親から子ウィジェット、子から親ウィジェットへのアクセスの仕方

August 13, 2024

親から子のフィールドにアクセス #

GlobalKey を使う #

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: MyParentWidget(),
  ));
}

class MyParentWidget extends StatelessWidget {
  MyParentWidget({super.key});

  final myChildWidgetKey = GlobalKey<MyChildWidgetState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Text('${myChildWidgetKey.currentState?.count ?? 0}'),
          IconButton(
            icon: const Icon(Icons.add),
            onPressed: () {
              myChildWidgetKey.currentState?.increment();
              // myChildWidgetKey.currentState?.increment(); を呼び出しても、MyParentWidget 自身は再ビルドされず、
              // myChildWidgetKey.currentState?.count の更新が画面に反映されない
              // MyParentWidget 自身を再ビルドするために markNeedsBuild(); を呼び出す
              (context as Element).markNeedsBuild();
            },
          ),
          MyChildWidget(
            key: myChildWidgetKey,
          ),
        ],
      ),
    );
  }
}

class MyChildWidget extends StatefulWidget {
  const MyChildWidget({super.key});

  @override
  State<MyChildWidget> createState() => MyChildWidgetState();
}

class MyChildWidgetState extends State<MyChildWidget> {
  int count = 0;

  void increment() {
    setState(() {
      count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return const SizedBox();
  }
}

子から親のフィールドにアクセス #

親からプロップスを受け取る #

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    home: MyParentWidget(),
  ));
}

class MyParentWidget extends StatefulWidget {
  const MyParentWidget({super.key});

  @override
  State<MyParentWidget> createState() => _MyParentWidgetState();
}

class _MyParentWidgetState extends State<MyParentWidget> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MyChildWidget(
      count: _count,
      increment: _increment,
    );
  }
}

class MyChildWidget extends StatelessWidget {
  const MyChildWidget({
    super.key,
    required this.count,
    required this.increment,
  });

  final int count;
  final VoidCallback increment;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Text('$count'),
          IconButton(
            icon: const Icon(Icons.add),
            onPressed: increment,
          ),
        ],
      ),
    );
  }
}

InheritedWidget を使う #

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    home: MyParentWidget(),
  ));
}

class MyInheritedWidget extends InheritedWidget {
  const MyInheritedWidget({
    super.key,
    required this.count,
    required this.increment,
    required super.child,
  });

  final int count;
  final VoidCallback increment;

  @override
  bool updateShouldNotify(MyInheritedWidget oldWidget) {
    return true;
  }

  static MyInheritedWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>()!;
  }
}

class MyParentWidget extends StatefulWidget {
  const MyParentWidget({super.key});

  @override
  State<MyParentWidget> createState() => _MyParentWidgetState();
}

class _MyParentWidgetState extends State<MyParentWidget> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MyInheritedWidget(
      count: _count,
      increment: _increment,
      child: const MyChildWidget(),
    );
  }
}

class MyChildWidget extends StatelessWidget {
  const MyChildWidget({super.key});

  @override
  Widget build(BuildContext context) {
    final counter = MyInheritedWidget.of(context);

    return Scaffold(
      body: Column(
        children: [
          Text('${counter.count}'),
          IconButton(
            icon: const Icon(Icons.add),
            onPressed: counter.increment,
          ),
        ],
      ),
    );
  }
}

findAncestorStateOfType で親にアクセスする #

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    home: MyParentWidget(),
  ));
}

class MyParentWidget extends StatefulWidget {
  const MyParentWidget({super.key});

  @override
  State<MyParentWidget> createState() => MyParentWidgetState();
}

class MyParentWidgetState extends State<MyParentWidget> {
  int count = 0;

  void increment() {
    setState(() {
      count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // const をつけると count が変わっても再ビルドされないため const を外す
    // ignore: prefer_const_constructors
    return MyChildWidget();
  }
}

class MyChildWidget extends StatelessWidget {
  const MyChildWidget({super.key});

  @override
  Widget build(BuildContext context) {
    final state = context.findAncestorStateOfType<MyParentWidgetState>()!;

    return Scaffold(
      body: Column(
        children: [
          Text('${state.count}'),
          IconButton(
            icon: const Icon(Icons.add),
            onPressed: state.increment,
          ),
        ],
      ),
    );
  }
}

GlobalKey を使う #

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: MyParentWidget(),
  ));
}

final myParentWidgetKey = GlobalKey<MyParentWidgetState>();

class MyParentWidget extends StatefulWidget {
  MyParentWidget() : super(key: myParentWidgetKey);

  @override
  State<MyParentWidget> createState() => MyParentWidgetState();
}

class MyParentWidgetState extends State<MyParentWidget> {
  int count = 0;

  void increment() {
    setState(() {
      count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // const をつけると count が変わっても再ビルドされないため const を外す
    // ignore: prefer_const_constructors
    return MyChildWidget();
  }
}

class MyChildWidget extends StatelessWidget {
  const MyChildWidget({super.key});

  @override
  Widget build(BuildContext context) {
    final state = myParentWidgetKey.currentState!;

    return Scaffold(
      body: Column(
        children: [
          Text('${state.count}'),
          IconButton(
            icon: const Icon(Icons.add),
            onPressed: state.increment,
          ),
        ],
      ),
    );
  }
}

その他参考 #

Flutter: assert と throw の使い分け

July 15, 2024

Dart 言語には assert という機能があります。assert の第一引数が条件式で、こちらが true であれば何も起きず、false であれば例外が throw されます。assert は Flutter のデバッグモードでのみ有効になります。リリースビルドを行った際は assert のコードをツリーシェイキングにより取り除かれ、ビルド後のコードからは除外されます。

...

Flutter: ツリーシェイキングの動作を確認

July 14, 2024

参考

DevTools でのアプリケーションのサイズ確認について

ツリーシェイキングについて

ビルド後のアプリケーションのサイズの確認方法 #

Flutter のプロジェクトを作成し、以下コードのビルド後のサイズを確認するとします。

...

Dart: Stream の使い方

July 13, 2024

Flutter において Dart の Stream はなくてはならない存在。たとえ開発者が意図的に使っていなかったとしても、ステート管理ライブラリの屋台骨は Stream が支えています。

StreamController() #

StreamController() でコントローラを生成し、それに sink.add() することでストリームにイベントを流すことができます。コントローラを stream.listen() して、コールバック関数を定義することで、流れてきたイベントを捕捉することができます。stream.listen()onError()onDone() を定義することで、それぞれエラー時のイベント、ストリームが閉じられる時のイベントを捕捉することができます。

...

University of London / MSc Computer Science: Project(6〜12 週目)

July 3, 2024

ロンドン大学で MSc Computer Science: Project モジュールを履修中。

講義内容に関して記録した個人的なスタディノートです。

全 24 週のうち 6〜12 週目の内容を記録します。(6 週目開始:2024 年 5 月 13 日 / 12 週目終了:2024 年 7 月 1 日)

...

Flutter: ウィジェットテストでスクリーンショットをとる

June 12, 2024

素敵なブログ記事を見かけたので記録。ウィジェットテスト中に、そのときのウィジェットのスクリーンショットを画像で取得することができます。

以下は flutter create 直後のウィジェットテストのコードに対して、スクリーンショットを取得するコードを追加した例です。flutter test を実行すると screenshots ディレクトリが作成され、その中にスクリーンショットが格納されます。

...

Flutter: debugFillProperties() でデバッグを楽にする

June 10, 2024

StatefulWidget(の State)は debugFillProperties() メソッドを持っています。これをオーバーライドするとデバッグが少しだけ楽になります。

これを使うことで何がどう変わるのかは以下の Stack Overflow が分かりやすいです。

...

Dart: Self タイプの実現方法

June 9, 2024

現在 Dart には Self 型を表すようなキーワードはありません。キーワードはないものの、Self 型を実現することはでき、具体的には次のように書けば実現可能です。若干奇妙な書き方です。

...

Flutter: ウィジェットテストの動きをシミュレータで表示する

June 8, 2024

通常のテスト方法 #

flutter create でアプリを作成すると次のテストファイルが作られています。

myapp/test/widget_test.dart

// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.

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

import 'package:myapp/main.dart';

void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(const MyApp());

    // Verify that our counter starts at 0.
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    // Tap the '+' icon and trigger a frame.
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    // Verify that our counter has incremented.
    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  });
}

テストを実施するには以下コマンドを実行すれば良いです。

...

Flutter: ライフサイクルメソッドの実行順番

June 7, 2024

次のメソッドの実行順番を確認するためにログを出力させてみた。

  • createState()
  • build()
  • setState()
  • initState()
  • didChangeDependencies()
  • didUpdateWidget()
  • deactivate()
  • dispose()
import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({
    super.key,
  });

  @override
  State<MyApp> createState() {
    print("MyApp.createState()");
    return _MyAppState();
  }
}

class _MyAppState extends State<MyApp> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
    print("MyApp.setState()");
  }

  @override
  void initState() {
    print("_MyAppState.initState()");
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print("_MyAppState.didChangeDependencies()");
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(covariant MyApp oldWidget) {
    print("_MyAppState.didUpdateWidget()");
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    print("_MyAppState.deactivate()");
    super.deactivate();
  }

  @override
  void dispose() {
    print("_MyAppState.dispose()");
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print("_MyAppState.build()");

    return MaterialApp(
      home: Scaffold(
        body: Column(
          children: [
            if (_count < 3) ...[
              MyCountWidget(count: _count),
              MyButtonWidget(onPress: _increment),
            ] else
              const MyDoneWidget(),
          ],
        ),
      ),
    );
  }
}

class MyCountWidget extends StatefulWidget {
  const MyCountWidget({
    super.key,
    required this.count,
  });

  final int count;

  @override
  State<MyCountWidget> createState() {
    print("MyCountWidget.createState()");
    return _MyCountWidgetState();
  }
}

class _MyCountWidgetState extends State<MyCountWidget> {
  @override
  void initState() {
    print("_MyCountWidgetState.initState()");
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print("_MyCountWidgetState.didChangeDependencies()");
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(covariant MyCountWidget oldWidget) {
    print("_MyCountWidgetState.didUpdateWidget()");
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    print("_MyCountWidgetState.deactivate()");
    super.deactivate();
  }

  @override
  void dispose() {
    print("_MyCountWidgetState.dispose()");
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print("_MyCountWidgetState.build()");

    return Text("count: ${widget.count}");
  }
}

class MyButtonWidget extends StatefulWidget {
  const MyButtonWidget({
    super.key,
    required this.onPress,
  });

  final VoidCallback onPress;

  @override
  State<MyButtonWidget> createState() {
    print("MyButtonWidget.createState()");
    return _MyButtonWidgetState();
  }
}

class _MyButtonWidgetState extends State<MyButtonWidget> {
  @override
  void initState() {
    print("_MyButtonWidgetState.initState()");
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print("_MyButtonWidgetState.didChangeDependencies()");
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(covariant MyButtonWidget oldWidget) {
    print("_MyButtonWidgetState.didUpdateWidget()");
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    print("_MyButtonWidgetState.deactivate()");
    super.deactivate();
  }

  @override
  void dispose() {
    print("_MyButtonWidgetState.dispose()");
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print("_MyButtonWidgetState.build()");

    return IconButton(
      icon: const Icon(Icons.add),
      onPressed: widget.onPress,
    );
  }
}

class MyDoneWidget extends StatefulWidget {
  const MyDoneWidget({
    super.key,
  });

  @override
  State<MyDoneWidget> createState() {
    print("MyDoneWidget.createState()");
    return _MyDoneWidgetState();
  }
}

class _MyDoneWidgetState extends State<MyDoneWidget> {
  @override
  void initState() {
    print("_MyDoneWidgetState.initState()");
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print("_MyDoneWidgetState.didChangeDependencies()");
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(covariant MyDoneWidget oldWidget) {
    print("_MyDoneWidgetState.didUpdateWidget()");
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    print("_MyDoneWidgetState.deactivate()");
    super.deactivate();
  }

  @override
  void dispose() {
    print("_MyDoneWidgetState.dispose()");
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    print("_MyDoneWidgetState.build()");

    return const Text("Done");
  }
}

ログ

...

Flutter: ウィジェットをキャッシュしておく

June 5, 2024

なんとなく思いついたのでコードをメモ。

一度作成したウィジェットをキャッシュに保存しておいて、必要になったときだけウィジェットを再構築する。

次の例で行くと、3の倍数かそうでないかで表示を切り替えている。何も考えないと数字がカウントアップされるごとに SanNoBaisuWidget ウィジェットが build() メソッドで読んでいる中身のウィジェットを構築されることになるが、本当にウィジェットを再構築する必要があるのは「奇数から偶数」「偶数から奇数」に切り替わるタイミングのみである。

...

Flutter: ChangeNotifier、ValueNotifier、Stream を使ったカウンターアプリ

June 3, 2024

こんな感じです。

ChangeNotifier #

import 'package:flutter/material.dart';

class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

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

class MyApp extends StatelessWidget {
  MyApp({
    super.key,
  });

  final Counter _counter = Counter();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyWidget(
        counter: _counter,
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  const MyWidget({
    super.key,
    required this.counter,
  });

  final Counter counter;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ListenableBuilder(
              listenable: counter,
              builder: (context, child) {
                return Text('${counter.count}');
              },
            ),
            IconButton(
              onPressed: counter.increment,
              icon: const Icon(Icons.add),
            )
          ],
        ),
      ),
    );
  }
}

ValueNotifier #

import 'package:flutter/material.dart';

class Counter {
  final ValueNotifier<int> count = ValueNotifier<int>(0);

  void increment() {
    count.value++;
  }
}

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

class MyApp extends StatelessWidget {
  MyApp({
    super.key,
  });

  final Counter _counter = Counter();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyWidget(
        counter: _counter,
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  const MyWidget({
    super.key,
    required this.counter,
  });

  final Counter counter;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ValueListenableBuilder(
              valueListenable: counter.count,
              builder: (context, value, child) {
                return Text('${counter.count.value}');
              },
            ),
            IconButton(
              onPressed: counter.increment,
              icon: const Icon(Icons.add),
            )
          ],
        ),
      ),
    );
  }
}

ちなみに ValueNotifier は単一の値のみを保持する場合における ChangeNotifier をよりシンプルに使いやすくしたものです。そのため、機能的には ChangeNotifier で実装した場合とほとんど違いありません。

...

Flutter: カウンターアプリを InheritedWidget で実装してみる

June 1, 2024

自己理解ために InheritedWidget でのデータ保持&アクセスをステップバイステップで実装していきます。

1. InheritedWidget で保持するデータに子ウィジェットからアクセスする #

count のデータを保持する InheritedWidget を定義しておき、ルートのウィジェット付近に InheritedWidget を配置します。こうすることで、InheritedWidget の配下に位置するウィジェットは .of(context)count を取得できるようになります。

...

GitLab のリポジトリを GitHub にミラーリングする

May 31, 2024

現在このブログの記事は GitLab で管理しており、ブログも Gitlab Pages でホスティングされています。バックアップも兼ねて、ブログのデータを GitHub にミラーリングしておこうと思います。

...