半農半エンジニアの記録

関西在住エンジニア。個人で勉強・開発したこと、その他趣味のことを書いてます。農業してます。

【読書録】プログラムはなぜ動くのか

未経験からソフトウェアエンジニアになり7年。
仕事ができる程度のプログラミングはできるようにはなったけど、CPUとかメモリとかプログラムの仕組みに関する知識をしっかり学んだことがないのでちゃんと勉強してみることにした。

第一弾はこちら

プログラムはなぜ動くのか 第2版 知っておきたいプログラムの基礎知識

プログラムはなぜ動くのか 第2版 知っておきたいプログラムの基礎知識

  • 作者:矢沢久雄
  • 出版社/メーカー: 日経ソフトウエア
  • 発売日: 2007/04
  • メディア: 単行本(ソフトカバー)

選んだ理由

とりあえず本屋でいくつか目を通してみて

  1. 随所に図が挿入されていて取っつきやすそう
  2. 昔の本だけど版数が多い

といったあたりで一冊目として読むにはいいのかな、と判断。

概要

タイトルの通りプログラムがなぜ動くのか、つまり人間が書くプログラムを PC がどうやって理解し実行しているのか、という内容。
高水準言語をコンパイルして機械語に翻訳されるとどう変わるのか、C言語をアセンブリ言語に変換して比較しながら解説している。
またCPUの構成(主にレジスタ)とともに、読み込んだコードがCPU、メモリを駆使して実行される流れの説明がされている。

感想

はじめに読む本としては間違ってなかったかな、という印象。
前述の通り図を使って説明してたり、内容もそこまで深入りしない内容だったので合間の細切れな時間でも読み勧めやすかった。
アセンブリ言語を使って比較しながら説明されるのでわかりやすい。
一方、やはり古い内容の本であるため内容があてにならない部分も散見される(PC のメモリは 2 GB くらい、最新の Windows は Vista だ、とか)。このあたりは流石に改定してほしい。
軽くつまむ程度には良いが、しっかり勉強するには物足りない。

キーワード

CPU
メモリ
レジスタ
高水準言語
アセンブリ言語
コンパイル
機械語

Flutter で作ったアプリに広告表示する(firebase_admob)

Flutter で作ったアプリで広告収入を得るために、admob ツールを使った。 firebase_admob という公式チームが作成しているライブラリがあるので、そちらを利用する。

pub.dev

気になったこと

アプリに広告を表示するまでの手順は、こちらのブログに書かれている手順に沿って設定できた。

[Flutter] AdMob を使ってバナー広告を表示する方法 │ Web備忘録

バナー広告でやってみて設定・表示するだけだと問題なくできたが、ちょっと工夫・考慮が必要な部分があった。
以下、画面下バナー広告を表示した場合の話

縦・横表示対応に向いていない

横向き(landscape)だと必然的に高さが小さく、広告でそれがさらに小さくなる。UI・UX 的に難ありなので縦(portrait)固定にした。

void main() {
  FirebaseAdMob.instance.initialize(appId: appId);
  // バナー広告を表示する
  myBanner
    ..load()
    ..show(
      // ボトムからのオフセットで表示位置を決定
      anchorOffset: 0.0,
      anchorType: AnchorType.bottom,
    );

  // 縦固定
  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);

  runApp(MyApp());
}

位置調整が必要

ライブラリにより表示される広告は、Flutter の世界からは切り離されている。つまり Widget ではない。なので普通に画面いっぱい表示させると Widget の上に広告が表示される。

例えば Floating action button はデフォルトで右下(bottomEnd)に表示されるが、広告がその上に表示されるので、ボタンは完全に隠れてしまう。

なので Padding を追加して広告を避けさせる必要がある。 *1

Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        ...
      ),
      body: Container(
        ...
      ),
      floatingActionButton: Padding(
        padding: const EdgeInsets.only(bottom: 90.0),
        child: FloatingActionButton(
          ...
        ),
      ),
    );
  }

キーボード出現時の位置調整

上記の位置調整をしたとき、更に問題が出てくるパターンがある。それは入力がある画面の場合だ。

キーボードが出てきたときに、Flutter のオブジェクトはキーボードが出てきた分を認識して下端がキーボードの上端になる。しかし広告は上記の通り Flutter とは切り離されており、かつキーボードを認識しない。そのため広告はキーボードによって隠されてしまう。

これで何が問題かというと、位置調整で Padding したぶんがそのまま適用されてしまっており、広告がないのに広告の分だけ Padding されたままになってしまうのである。
なのでこれを解決するには、キーボードが開いているかどうかを判定し、 Padding を動的に設定する必要がある。

ただし Flutter ではそのようなプロパティは用意されていないようなので、下記のpackage を使って対応することにした。*2

pub.dev

 @override
  void initState() {
    super.initState();
    KeyboardVisibilityNotification().addNewListener(
      onShow: () {
        _padding = EdgeInsets.all(0.0);
      },
      onHide: () {
        _padding = EdgeInsets.only(bottom: 90.0);
      },
    );
  }

という感じで違和感なく広告が表示できるようになったんじゃないかと思う。

*1:Scaffold の floatginActionButton は、FloatingActionButton を設定する必要はないので Padding でラップが可能

*2:MediaQuery.of(context).viewInsets.bottom の値で判定する方法を取っている人もいるようだが、Android でのみ動作し iOS では正常に動作しないらしい

Flutter の多言語対応(Intl)

Flutter で多言語対応した。

公式ドキュメントでもよく説明されていると思うし、 flutter.dev

こちらの mono さんの記事もすごく参考になる。
Dart/Flutter での多言語対応あれこれ - Flutter 🇯🇵 - Medium

今のところ DateFormat や NumberFormat は不要なので、文言をロケールに合わせて変更する部分だけ行った。

目次

パッケージのインストール

pubspec.yaml に追加。

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  flutter_cupertino_localizations: ^1.0.1

flutter_cupertino_localizations は iOS でちゃんと動作させたいなら入れなさい、とのこと。

追記したら flutter pub get を実行、もしくはAndroid Studio なら、Packages get をクリック。

Android Studio で Packages get をクリック

MaterialApp修正

import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_cupertino_localizations/flutter_cupertino_localizations.dart';

MaterialApp(
 localizationsDelegates: [
   // ... app-specific localization delegate[s] here
   GlobalMaterialLocalizations.delegate,
   GlobalWidgetsLocalizations.delegate,
   GlobalCupertinoLocalizations.delegate,
 ],
 supportedLocales: [
    const Locale('en'), // English
    const Locale('ja'), // Japanese
    // ... other locales the app supports
  ],
  // ...
)

localizationsDelegates

ローカライズした value を使うためのファクトリを指定する。

  • GlobalMaterialLocalizations.delegate
    文字などの文言や他の MaterialApp コンポーネントようライブラリ
  • GlobalWidgetsLocalizations.delegate
    文字列の方向(英語は左から右、アラビア語は右から左、みたいな)を定義するためのライブラリ

ということなのでおまじない的に両方入れておけばいいのかな。アラビア語とか対応しない限りは GlobalWidgetsLocalizations.delegate は無くてもいいのかはわからない。(試してない)

supportedLocales

アプリが対応する言語のロケールを指定する。
中国の繁体字/簡体字みたいに方言?がある場合はLocale.fromSubtags(languageCode: 'zh') という感じでできるらしいが、今回は日本語と英語のみなので省略。

ローカライズリソースの定義

class DemoLocalizations {
  DemoLocalizations(this.localeName);

  static Future<DemoLocalizations> load(Locale locale) {
    final String name = locale.countryCode.isEmpty ? locale.languageCode : locale.toString();
    final String localeName = Intl.canonicalizedLocale(name);
    return initializeMessages(localeName).then((_) {
      return DemoLocalizations(localeName);
    });
  }

  static DemoLocalizations of(BuildContext context) {
    return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
  }

  final String localeName;

  String get hello {
    return Intl.message(
      'こんにちは',
      name: 'hello',
      locale: localeName,
    );
  }
}

initializeMessages() がローカライズされたメッセージカタログで読み込み、intl パッケージが インポートする。
Intl.message() でそれらを読み込んで返している。
という流れ。 メッセージカタログは、後述する intl のツールで作成できる。

多言語対応(メッセージカタログの作成)

intl のツールを利用する。

arb ファイル作成

 flutter pub run intl_translation:extract_to_arb --output-dir=lib/l10n lib/main.dart

main.dart 内にある Intl.message() ごとに定義を作成できる。
lib/l10n の下に intl_messages.arb という JSON ファイルが出力される。
複数言語対応するときは、この arb ファイルをベースに intl_ja.arb や intl_en.arb を作成する。

dart ファイル作成

 flutter pub run intl_translation:generate_from_arb \
    --output-dir=lib/l10n --no-use-deferred-loading \
    lib/main.dart lib/l10n/intl_*.arb

上記で作成した arb ファイルごとに、 intl_XXX_all.dart が作成される。
この dart ファイルの中身がロケールに応じて使われる。

使ってみる

print(DemoLocalizations.of(context).hello);

みたいにするだけ。
デフォルトロケールが日本と想定して、

こんにちは

ロケールを変更したい場合は、 DemoLocalizations.load(Locale('en')); としてやる。

DemoLocalizations.load(Locale('en'));
print(DemoLocalizations.of(context).hello);
hello

その他

とりあえず公式ページにあるコードをサンプルとして掲載。
後日 git にサンプルあげる。

Flutter の pop による戻り値

Flutter のルーティングで push / pop する時の扱い

スタックでのルーティング

概略

  • push 時に return で受け取りたい型を指定
  • pop 時に指定された型を return
  • pop の引数を省略すると null が返る
  • 戻るボタンでも null が返るので、必ず考慮が必要

ページ遷移(進む)

push で積む。
Navigator.of(context).push
を使う。

return で受け取りたい型を指定してやればいいので、 int を指定してみる。

int ret = await Navigator.of(context)
    .push(MaterialPageRoute<int>(builder: (BuildContext context) {
  return SecondPage();
}));

ページ遷移(戻る)

pop でスタックから取り除く。
Navigator.of(context).pop
を使う。

この時、 push 側で受け取りたい戻り値を、 pop() の引数で渡す。

int _counter;
~~ 中略 ~~
Navigator.of(context).pop(_counter)

引数を省略すると null が返される。

デフォルトの戻る機能

OS の戻る機能を使うと、引数を省略した時と同じで null が返される。

back button in Android
back_button

なのでそこは考慮しておく必要がある。逆に言えば、戻る機能と同じ挙動をさせたければ、引数を省略して null を返せばいい。

コードサンプル

全体のコード

simple push and pop demo with return