ちょっとうろうろ

思いつくままちらほらと

Flutter やってみた

flutter で手頃にスマホアプリが作れると聞いたので、試しにやってみることにした。

まずは Flutter のコード打ち込んで動くようになるのを目指す。

環境は maxOS です。

インストール

素直に公式チュートリアルに従って進める。

適当な所にダウンロードした zip を配置して、

export PATH=$PATH:`pwd`/flutter/bin

するか、.bash_profile

export PATH=$PATH:[flutterのパス]/flutter/bin

を追加してパス通せば flutter コマンドが使えるようになる。

で、セットアップのために下記のコマンドを実行する。

flutter doctor

そうすると、flutter で開発を行うのに必要なツールを教えてくれる。

ちなみに xcode のライセンス認証だけは先にしておかないとそれ以外何も教えてくれないので注意

個々のツールのセットアップ方法に関しては、公式チュートリアルに説明があるのでそちらに従ってやれば AndroidiOS 双方の開発環境が整う。

とりあえず VS Code と Connected device 以外チェックが付けばOKか?

やったこと

流すのもあれなので一応やったことを覚え書き

Android

とりあえず公式から Android Studio をダウンロード。エディタが必要ないなら sdk だけ入れれば良さそうだけど、今回は全部入れた。記事書いてる時点では ver.3.2 だ。

インストール後、とりあえず適当にプロジェクト作って動作することを確認、Android toolchain の項目にもチェックが付くはず。

自分はこれに加えて Android licenses not accepted と言われたので、flutter doctor --android-licenses を実行したらチェックが付いた。

それから、Flutter と Dartプラグインを入れる。Android Studio を起動した後、Configure -> Plugins を開く。そして Browse Repositories で Flutter を検索すると、Flutter というプラグインがあるはずなので、これをインストール。すると同時に Dart をインストールするか訊かれるので、一緒にインストールする。その後、Android Studio を再起動すれば、プラグインが使えるようになるはず。

Android Studio の項目から×がなくなれば OK。

iOS

XcodeHomeBrew が必要なので、なければインストールしておく。

その後、doctor の指示に従い、

brew update
brew install --HEAD usbmuxd
brew link usbmuxd
brew install --HEAD libimobiledevice
brew install ideviceinstaller
brew install ios-deploy

としていけば OK。

サンプルプロジェクト

今回は VS code でやってみた。

コマンドパレットで Flutter: New Project を選択し、適当なフォルダにプロジェクトを作成してみる。これだけで必要な環境が揃う。最近は楽で良い。

flutter を実行する前に、任意のエミュレータを起動しておく必要がある。flutter emulators でインストールされているエミュレータのリストが見られる。これらの内のどれかを flutter emulators --launch <emulator_id> 起動しておく。

その後、flutter run を実行すると、起動中のエミュレータに作成したアプリがインストールされ、動作を見ることができるようになる。

ちなみにホットリロードを行うには起動した上で r を、ホットリスタートを行うには R をタイプすれば良い。実行を止めるには q だ。いいね。

試しにホットリロードをやってみよう。チュートリアルに従い、

home: MyHomePage(title: 'Flutter Demo Home Page'),

home: Scaffold(
appBar: AppBar(
    title: Text('Welcome to flutter')
),
body: Center(
    child: Text('child')
),

にして保存し、ホットリロード。するとエミュレータ上のアプリではボタンが消え、中央にテキストが表示される。ちゃんと動いてるね。

デバッグビルドにも少し時間がかかるので、ホットリロードするのがおすすめ。ところで保存にフックする方法はないんだろうか。

また、外部パッケージを pubspec.yaml に追加した場合も、VS Code だと自動で flutter packages get が実行された。有能かよ。

まとめ

Flutter の苦手分野とかまだ見えてないのでまだなんとも言えないが、簡単なアプリ作るのに必要な機能は一通り揃ってる感じなんだろう。これからもちょっとずつ触ってみよう。

Electron メモ帳(1)

最近仕事で Electron アプリを書いている。ちょっと昔に触ったときには npm install electron-prebuilt じゃないとうまくインストールできないし、同じデスクトップアプリ書くなら .NET Framework でいいじゃん? って感じだったのでそこまで深くやらなかった。けど、改めて触ってみると勝手が全然違って面白いね、って思った。

簡単に Electron を説明するなら、NodeJS と Chromium で動くデスクトップアプリケーションを開発するためのフレームワークで、Web 系の知識を使って開発可能であり、マルチプラットフォームに出力ができる。最近のフレームワークマルチプラットフォームが多いよね。楽でいいけど。

要するに、今までブラウザで動かしていた Web の資産を流用してスタンドアロンなアプリケーションを作ったり、Web 系のノウハウを利用してデスクトップアプリを作りたかったり、様々なプラットフォーム向けの開発をやりたい場合には便利なわけだ。ちなみに AndroidiOS 用のアプリも作れるらしいけど、今回開発対象じゃなかったからよく調べてない。

今はというと、Typescript+AngularJS で書いて Webpack でビルドしている。なんか Webpack のビルド対象にしれっと Electron がいて、それをターゲットに設定しておくと NodeJS のモジュールが使えたりする。調べると中々色んなことができるのが分かって、非常に楽しい。

ということで、書きっぱなしというのもなんだし、やったことは覚書しておこうと思う。


まずは Electron の導入から。npm と NodeJS は入ってる前提でいいでしょう。どうせ NodeJS 入れとけば勝手に npm も入るし。新しい物好きの人は Yarn 使ってもいいんじゃないかな(どっちにしろ npm は入れることになるだろうけど)。

とりあえず npm install electron で Electron をインストールする。パッケージングするなら electron-packager ってモジュールが必要なんだけど、今回はサンプル作るだけなので入れなくても構わない。

入れられたら、とりあえず Hello World してみるんだけど、その前に Electron の仕組みを軽く洗っておく。

Electron は二つのプロセスを駆使して動いている。メインプロセスとレンダラプロセスだ。メインプロセスとは、Electron 本体を動かしているプロセスで、ウィンドウの作成やバックグラウンドで行う処理を担っている。一方レンダラプロセスは、ウィンドウに表示するコンテンツを制御するプロセスである。要するに、ブラウザとウェブページの関係だと思ってもらっていいと思う。

Electron アプリを動かすためには、これら双方のコーディングを行う必要がある。といっても、レンダラプロセスは(ウィンドウ間の通信やログイン処理などをしないのであれば)普通のブラウザ向けのページを作るのと大差ない。よって、適当な HTML の入門サイトに行って Hello World のコードをパクってくれば問題ない。

メインプロセスは、次のように書く必要がある。

// Electron モジュール本体を読み込み
const electron = require('electron');

// Electron モジュールの機能を個別に取得
const {app, BrowserWindow} = electron;

// window のインスタンス用変数
let mainWindow = null;

// window を生成する関数
function createMainWindow(){

    // 新しい window を作成
    mainWindow = new BrowserWindow({
        width: 800,
        height: 600
    });

    // window が開く URL の指定
    mainWindow.loadURL(`file://${__dirname}/index.html`);

    // Developer Tool を開く操作
    mainWindow.webContents.openDevTools();

    // window を閉じたときの挙動を指定
    mainWindow.on('closed', () => {
        mainWindow = null;
    });
}

// window が開始された時の挙動を指定
app.on('ready', createMainWindow);

// すべての window が閉じたときの挙動を指定
app.on('window-all-closed', () => {
    if(process.platform !== 'darwin'){
        app.quit();
    }
});

最後に、package.json"main" がメインプロセスの js ファイルになっていることを確認しておく。Electron は起動時にここを見るようになっている。

Electron を起動するには、node_modules の .bin ディレクトリ内にある electron ファイルを実行する。グローバルにインストールしてパスを通しているなら(多分)electron とだけ打っても起動するはず。毎回入力するのが面倒だったら、package.json の scripts にでも書いておくことを進める。

ちゃんと書けていれば、ウィンドウが立ち上がって index.html の内容が表示されるはずだ。


とりあえず導入はこんなところだと思う。単純に単一ウィンドウでやるなら、Web なり Node なりの知識を駆使して書けると思う。

もっと Electron っぽい話については、もうちょっと知見が溜まったら(あと気が向いたら)書こうと思う。

Firebase 触ってみた

最近仕事で Firebase を触る機会があって、色々試してみたので忘れないうちにメモ代わりに書き残しておこうと思う。

こういうのって触らないとすぐ忘れちゃうしね。。。


Firebase とは

Google が運営している MBaas(Mobile Backend as a Service だったっけ?)。2011 年に始まったサービスで 2014 年に Google が買収して一気に機能が追加されたみたいだけど、私が知ったのは最近のことなのでそこまで深くは知らない。HP はここ

要するに、バックエンド側でやるべき面倒なサービスを肩代わりしてやりやすくしてくれるものだ。Web アプリなので、もちろんブラウザ上で操作することになるが、触った感じ、ロードは若干長いけどレスポンスは問題ないレベルだと思う。

基本的な機能には以下のものがある。

  • Analytics
  • Develop
    • Realtime Database
    • Authentification
    • Cloud Messaging
    • Storage
    • Hosting
    • Test Lab
    • Crash Reporting
  • Grow
    • Notifications
    • Remote Config
    • App Index
    • Dynamic Links
    • Invites
    • AdWords
  • Earn
    • AdMod

大別すれば、解析・開発・運用・収益辺りになると思うけど、Analytics は左の機能リストのトップにあるし、メイン機能っぽく見える。

f:id:AmayaSusuki:20161005201148p:plain WEB の画面はこんな感じ。

当然のことだけど、アプリを登録して端末側で実際に使用し、レポートなりなんなりを送ってもらわないと使えない。

ということで、導入方法から見ていこうと思う。私がやったのは Android アプリなので、iOS とか Web アプリのやり方は別の所で見てください。そんなにやり方は変わらないと思うけど。

Firebase の導入

導入方法はいたって簡単で、というか、Android Studio を使っていれば何の問題もなく導入できる(と思う)。

とりあえず Firebase

方法としては二種類あって、

  • Android Studio からアプリと Firebase を接続する
  • Firebase プロジェクトにアプリを登録する

のどちらかをやればいい。まぁどちらかと言えば前者のが簡単だと思う。自動でやってくれるし。

Android Studio にやってもらう方法

Android StudioTools -> Firebase を選択すると、Assistant タブが開く。そこから任意の項目を選んで、手順に従って操作すれば Firebase との接続が大体完了する。

各項目を開くと、① に Connect your app to Firebase の項目があるので、その直下の Connect ボタンを押下する。Firebase にサインインしていない場合、ここでサインインを行う。

サインイン後、新しい Firebase プロジェクトを作成するか、既存の Firebase プロジェクトを使用するかを選択する。

その後、Connect to Firebase ボタンを押下する。上手く接続できれば、ダイアログが閉じるはず。

Firebase プロジェクトからアプリを登録

Firebase プロジェクトのメインページには Android, iOS, Web アプリを登録するフォームがそれぞれある。ここで登録したい種類のアプリのボタンを押下する。つまり、今回は Android アプリを選ぶ。

するとダイアログが開き、登録するアプリのパッケージ名と、SHA-1 証明書を入力するフォームが表示される。適切に入力し、アプリを登録を押下して次に進む。

次の画面に遷移すると、自動的に google-services.json ファイルのダウンロードが開始される。ダウンロードしたファイルを Android アプリの App ディレクトリに追加し、続行ボタンを押下する。

最後に、表示された手順に従って、Android アプリの build.gradle ファイルを編集し、Android Studio に表示されている Sync now を押下すれば、アプリが登録されるだろう。

正直、この辺はやってなくて、「こうやればできそう」ってだけなので、後で確認した方が良いかな。。。


ここまでやってアプリが登録できれば、後はアプリ側で Firebase を利用できるように設定・実装を行えばいい。

まずはAndroid プロジェクトの設定を行おう。

Android プロジェクトの設定

build.gradle を編集して、Firebase をアプリから利用できるようにする必要があるんだけど、Firebase の利用にはネット接続が必須なので、そのためのパーミッションを設定する必要がある。

なので、AndroidManifest.xml 開き、<manifest> の下に以下のパーミッションを追加する。

<manifest **省略**>
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
    <uses-permission android:name="android.permission.WAKE_LOCK"></uses-permission>

    **省略**
</manifest>

後は使いたい機能のライブラリを build.gradle に追加してやれば Firebase の API が利用可能になる。

この辺は、Android StudioTools -> Firebase で開く Assistant タブに記載されてる手順に従えばできると思う。

一応今回やってみた機能(Analytics, Crash Reporting, Notification)を手動で追加するなら次のようになる。

build.gradle(Project: App名 を開き、以下の記述を追加する(バージョンは適宜変更すること)。

build.script {
    ** 省略 **
    dependencies {
        ** 省略 **

        classpath 'com.google.gms:google-services:3.0.0'
    }
}

build.gradle(Module: App) を開き、以下の記述を追加する(バージョンは適宜変更すること)。

** 省略 **
dependencies {
    ** 省略 **
    compile 'com.google.firebase:firebase-core:9.6.1'
    compile 'com.google.firebase:firebase-crash:9.6.1'
    compile 'com.google.firebase:firebase-messaging:9.6.1'
}

上記の設定を追加後、APK のビルドに成功することを確認する。


設定が終わったら実際にコーディングする……が、Notification はコーディングなしでいける。

Firebase プロジェクトの Notification で送信メッセージを作成し、送りたいアプリを選択して送信するだけだ。とってもお手軽。

だけど、こういう条件で Notification を送信とか、そういう機能が見当たらないんだよね……もうちょっと調査が要るかも。

それと、Notification 以外の Analytics や Crash Reporting は端末側が Google アカウントを設定していること、そして Google Play Service を無効にしていないことが必要になる。特に後者は色々目の敵にされてるアプリだけど大丈夫なんだろうか。


さて肝心のコーディング。

まず、FirebaseAnalyticsインスタンスを作成する。

public class MainActivity extends AppCompatActivity {

    private FirebaseAnalytics mAnalytics;

    @Override
    protected void onCreate(@Nullable Bundle saveInstanceState) {
        super.onCreate(saveInstanceState);
        setContentView(R.layout.activity_main);

        // インスタンスの作成
        mAnalytics = FirebaseAnalytics.getInstance(this);

        // 必要ならユーザプロパティを設定
        mAnalytics.setUserProperty("key", "value");
    }
}

このインスタンスからログイベントの送信ができる。イベントログの送信はこんな感じ。

 private void sendEventLog() {
        Bundle params = new Bundle();                           // 送信パラメータ用インスタンスの作成
        params.putString(FirebaseAnalytics.Param.ITEM_ID, "id");            // イベントログの ID を設定
        params.putString(FirebaseAnalytics.Param.ITEM_NAME, "name");            // イベントログの名称を設定
        params.putString(FirebaseAnalytics.CONTENT_TYPE, "type");           // イベントログのデータ型を設定
        mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, params);    // イベントログを送信
    }

params.putString メソッドで送れる情報は色々あって、何やらゲームっぽいパラメータ名なんだよね。Google Play Service のパラメータを継承してるんだっけか。まぁどっちにせよ、このメソッドで作った文字列は実は Firebase では見られなかったりする。見るには BigQuery とかいうのとリンクする必要があるんだとか。なんだよそれ。。。

また、イベントログが実際に反映されるのは、一日一回程度。最初の更新はアプリが最初に起動されてから二十四時間以内とのことらしい。Analytics 関連はリアルタイムで情報を反映するのは難しいのかな。情報量も半端ないだろうし。


Crash Reporting は自動で送信されるパターンと手動で送信するパターンがある。自動で送信されるパターンはとてもシンプルで、アプリのクラッシュ時にクラッシュレポートが送信される。

一方で、手動で送信する場合は以下のような記述をする必要があるだろう。

private sendCrashReporting(){
    FirebaseCrash.log("ID", "Description", "type"); // 送信したいログがあれば記述
    FirebaseCrash.logcat(type, "tag", "label"); // アプリのログから必要なログを抽出
    FirebaseCrash.report(Exception);        // レポートを送信
}

log メソッドは適当な文字列を、logcat は ADB のログから抽出した文字列をレポートのログに書き込むためのものだ。report すればそれまでに蓄積したすべてのログと一緒にレポートを送信できる。

ちなみに、単に Exception をレポートすると空欄だが、引数に文字列を入れて送信するとその文字列がちゃんと出る。


以上のような感じで、単にログやレポートを送信したいだけならかなり手軽にできる。

問題は解析結果が Firebase 上でどう表示されるかだろうか。

Analytics では、アプリの起動状況から得られるのはユーザの性別や年代といった大まかなユーザ情報や、アクティブユーザやユーザ維持率といった稼働状況、収益などといった情報がある。サンプルアプリで試しただけだからそれほど多くのことは分からないけれど、結構大まかなところしか分からなそうという印象がある。特にイベント。こちらから送信したイベントの中身が見られないので、ちゃんと使わないと雑多なものが並ぶだけになってしまう。

というか、イベントの使い道はやっぱり、目標到達プロセス(あるイベントに到達したときに、特定のイベントをきちんと通っているかを確認するための指標)と組み合わせて使うものなんだろうか。そこらへん、まだあんまり見定められてない。

Crash Reporting についてはやっぱり見にくい。

送信されたクラッシュレポートは、イベントのカテゴリを判別して自動で分類してくれるんだけど、同じエラーは同じカテゴリに分類されるんだよね。つまり、色んな原因で出たはずの NullPointerException は、全て同じカテゴリに分類されてしまう。これってすごく見づらいよね。それ以上の分類をしろってのも酷かもしれないけど……。

この辺の問題は、レポートの仕方、すなわちどんなエラーログを送信するかを適切に設計することでなんとかなるとは思う。使い方によっては結構柔軟になってくれそうな気はしている。


まとめ

ログ解析とかレポート取得とか認証処理とかするだけなら頼りになるけど、サーバ側で色々処理するなら別のが必要だと思う。

今回実装してみた機能以外にもたくさん機能はあるし、無料版でグダグダ言うのもあれなので、もうちょっと遊んでみたい。Hosting 辺りとか。

wiki を markdown で記述する mdWiki が使いやすかった

markdown で適当に書くだけで簡単な Wiki が作れるツール mdWiki が便利だったのでメモ。

ダウンロード・インストール

MDWikiのドキュメント から「How does it work?」の download page リンクにとび、Get the Latest Release からダウンロードページに行ける。そこで最新バージョンをダウンロードすればいい。 単純に言うなら、mdwiki-0.6.2.zip をダウンロードする。

インストールは非常に簡単で、というよりも、mdwiki-0.6.2.zip を解凍したフォルダにある mdwiki.html を利用すれば実行できる。mdwiki.html の中身を変更する必要は一切ない。

内容の追加

mdwiki.html を開くと、メニューバーとまっさらなページが表示される。mdwiki.html はデフォルトのページとして index.md を開こうとするので、まず index.md を作成してみることにしよう。

基本的な記述の仕方は markdown 記法で問題ないけど、一部 MDWiki 特有の表記が必要だったりする。

そのファイルで最初の # はページタイトルと解釈され、ページ上部に表示される。それ以外の # は普通の見出しとして表示される。

見出し 2 である ## は見出しとして表示されるだけでなく、サイドメニューとして表示される。各項目をクリックすると、その項目のページ位置までジャンプすることができる。

後の記述は普通の markdown と同じ(はず)。MDWikiのドキュメント ではリンクや画像、シンタックスハイライトの記法が紹介されていますが、恐らく違いはない(はず)。

メニューの追加

wiki というからには、index.md だけでなく色々なページを表示させたい。リンクからとぶのもありだが、どうせなら上部のメニューバーからとべるようにしたい。

これを行うには、mdwiki.html と同じフォルダに navigation.md というファイルを配置する。メニューバーに新しく要素を追加したい場合は、このファイルを編集すればよい。

このファイルも markdown で記述するけど、フォーマットが若干特殊。

見出し 1 # はメニューバーの左端にタイトルとして表示される。これが複数あってもただ単に文字列が繋がるだけなので意味はそんなにない。

メニューバーに要素を追加したい場合は、[Menu Item]() のように書く。末尾の () に何も記述しない場合は、クリックしても何も起こらないパネルになるか、あるいはドロップダウンリストにリンクを追加すればそのページへのリンクとなる。

要素にドロップダウンリストを追加したい場合は、次のような記述が可能。

[Menu Item]()

  * # Sub Menu Title
  * [Sub Menu Name](Link)
  - - - -
  * …

サブメニューの行頭の*は必須だと思う。要素の記述とリストの記述の間には空行を入れる必要がありそう。- - - - は区切り線。

なお、メニューバーのスタイルを変更したい場合、Gimmicks という機能を利用することができる。 単に [gimmick:themechooser](Choose Style) と書けばドロップダウンリストが表示され、スタイルが変更できるようになる。


多分、使い方はこれだけで十分のはず。あとは使いたければチュートリアルを見れば画像とかの追加はできる。 個人や小規模なチームでの情報をまとめる程度ならこれで十分だと思う。が、実運用してないからなんとも言えない……。

まぁ、しばらく個人用で色々まとめてみたいと思ってる。

goroutine内の変数の扱い方

goroutine内の変数の扱い方

 

goroutineがまだうまく扱えないので、go func()内で変数がどういう処理をされるのかを調べてみる。環境によって出力順序は異なると思うので、その点は目をつぶって欲しい。

ベース

generate()関数から送信された数値を受信し、そのまま出力する極めて簡単な処理を行う。

main1.go

package main

import "fmt"

//数値の生成
func generate() chan int {
    out := make(chan int)
    
    for i:=0; i<10; i++ {
        go func() {
            out <- i
        }()
    }
    
    return out
}

//数値の出力
func main(){
    ch := generate()
    for i:=0; i<10; i++ {
        fmt.Println(<-ch)
    }
}

i番目の値を送信するだけの簡単な関数だ。

なお、この関数をそのまま実行すると、

output1
10
10
10
10
10
10
10
10
10
10

と出力される。
これは、go func()内のiに値が割り当てられないまま最後までgoroutineをスタックし続け、forを抜けたところで初めて値が割り当てられるためだろう。

ちゃんと1から10までの値を出力するためには、go func()に引数を与えてやる必要がある。

main2.go

func generate() chan int {
    out := make(chan int)
    
    for i:=0; i<10; i++ {
        go func(n int) {
            out <- n
        }(i)
    }
}
output2
2
0
1
5
4
8
6
9
3
7

並列実行なので順番はバラバラになる。

これを順番通りに出力するには、for文の外でカウンタ変数を設ける必要がある。
また送信より先にインクリメントしないと処理されない。

main3.go

func generate() chan int {
    out := make(chan int)
    c := -1
    
    for i:=0; i<10; i++ {
        go func() {
            c++
            out <- c
        }()
    }
}
output3
0
1
2
3
4
5
6
7
8
9

つまり、並列でも良い場合は内部カウンタ、順列がいい場合は外部カウンタにする必要がある。
……まぁ、後者はgoroutineの利点を捨ててる気もするけど

補足

generate()のforを多めに回すと、送信される値がまた違ってくる。

main4.go

func generate() chan int {
    out := make(chan int)
    
    for i:=0; i<=10; i++ {
        go func(n int) {
            out <- n
        }(i)
    }
}
output4
10
4
0
1
2
3
6
8
9
5

並列操作をするときに数値を間違えると大変なことになるということだ。