Floobitsを試してみた

Floobitsというのはリアルタイムコラボレーションを実現するためのサービス。 VSCodeのVisual Studio Live Shareみたいなやつで、リモート経由でのペアプログラミングとかを実現するためのサービスである。

リモート経由でペアプロしたいが、Android開発でそんなことできるのかというのが出発点だった。 Android StudioのベースとなっているIntelliJ IDEAでもそういう機能があるのか調べたところ、今の所ないというのが結論。 要望自体ははるか昔からあるみたいだが、実現するにはコラボレーション用のサーバ用意したりとハードルがあまりに高いだろうことは想像に難くない。 公式には用意されていないが、見つけたのがこのFloobitsというサービスである。

別にIntelliJに限らず、他のプラットフォームでもプラグインが用意されている。 https://floobits.com/help/plugins Emacs, Sublime Texxt, Neovim, IntelliJ, Atomと多数のエディタに対応している。 Free planであれば5つのWorkSpaceを持てる。 Android Stuioでいえば1つのプロジェクト=WorkSpaceになるだろう。 Free planではprivateなWorkSpaceを持つことはできないので、ソースコードは誰でも見れる状態になる。 Edit権限を与えなければ勝手に編集されることはない。 ちなみにWorkSpaceはActive Workspaceで確認できる。

どんな感じなのか気になったので、パソコン2台使ってとりあえず試してみた。 試した環境が同一LAN内にあるPCではあったことが関係している可能性はあるけれど、遅延はほぼないと思っていいだろう。

Summonなる機能があって、これを使えば他のコラボレーターを自分が編集しているファイルに瞬時に招集することが可能。 「このファイルがさー」「どれだよ?」 なんてときに活躍しそう。 とりあえず試したのはコード編集だけではあるが、他にもチャットができたりコラボレーションのための便利な機能が盛り込まれている。

ただ、ペアプロに便利とはいうものの、Floobitsはあくまでリアルタイムでのペアプロ目的に使うにとどめた方がいいだろう。 Floobitsにアップロードしたプロジェクトを、Floobitsに接続しない状態で更新→FloobitsのWorkSpaceに接続という流れになると、リモートのファイルを上書きするか、リモートのファイルでローカルを上書きするかの2択になってしまう。 Gitでバージョン管理していても、FloobitsのWorkSpace自体はGitで管理されているわけではないので、下手するとGitの履歴自体が上書きされてなかったことにされる恐れもありそう。 プロジェクトはGitで別途管理して、サブ的にFloobitsを使うという感じがいいのかもしれない。 ゼロからいきなりFloobitsのWorkSpaceにジョインして開発を進めるとおかしなことになりそう。

gitと併用してFloobitsを使う場合のFAQがあった。 https://floobits.com/help/faq 運用でカバーする必要があるっぽい。

リモート経由でペアプロするのには非常に便利だと思う。 またFloobitsはAndroidに限らず使えるというのは便利な点だろう。 セキュリティ的にどうなんだというところがクリアできるなら、普通に便利な気がする。

TouchDelegateを使ってタッチ可能領域を拡張する

Buttonのクリックに反応する範囲を拡張したい時がある。 今回は下図のようなカスタムViewを作りたかった。

Image

ImageButtonとTextViewを内包したViewである。 親レイアウトのViewGroupをタッチする=ImageButtonをタッチするという扱いにしたかった。

ImageButtonにしているのは、他のViewとの兼ね合いである。 他のViewはImageButtonにstyle="@style/Base.Widget.AppCompat.ActionButton"を適用して、アイコン画像の周りにだけリップルエフェクトがかかるようになっていて、このカスタムViewもそれに合わせたかったのである。

これはTouchDelegateを使うと実現できる。

TouchDelegate – Android Developers

使い方

TouchDelegateは親のViewGroupに設定する。

TouchDelegateのインスタンスを作成するには、タッチエリアを表すRectと委譲先のView(今回でいうとImageButton)への参照が必要。 Rectはローカル座標系(ディスプレイ上での絶対座標ではなく、ViewGroupの左上を0とした相対座標)を使う。 今回はViewGroupのgetDrawingRect()を使って取得したRectを使った。

ぐぐるとgetHitRect()を使った例がみつかったが、これだとうまく動かなかった。絶対座標になっているからだと思われる。

コード例

class CommentStatusView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {

    private lateinit var binding: ViewCommentStatusBinding

    init {
        binding = ViewCommentStatusBinding.inflate(LayoutInflater.from(context), this, true)
        doOnPreDraw {
            val rect = Rect()
            binding.viewGroup.getDrawingRect(rect)
            binding.viewGroup.touchDelegate = TouchDelegate(rect, binding.imageButton)
        }
    }

    override fun setOnClickListener(l: OnClickListener?) {
        binding.imageButton.setOnClickListener(l)
    }

}

Kotlinで、Android-ktxとDataBindingを使っている。

doOnPreDrawがAndroid-ktxを使っている部分で、ViewTreeObserverを利用してViewの大きさが決まった後で中のブロックの処理(TouchDelegateの設定)を行っているだけである。

後はこのカスタムViewに対するsetOnClicklistenerをImageButtonに対して設定するようにしている。

ポイントは

Read full post gblog_arrow_right

TextViewに設定したテキスト内のURLに遷移する

TextViewに設定したテキスト内にURLがあった場合に、そのリンクをクリックできるようにしたい。 クリックしたらブラウザが開いて該当ページに移動できるようにしたい。

手っ取り早くこの要望を満たそうと思ったら、TextViewには便利な機能が用意されている。 TextViewにandroid:autoLink="web"を追加すればよいだけである。 これでテキスト内のURLをクリックしたらブラウザが開いてくれる。 めでたしめでたし。

といくなら楽でよかったのだが、この機能によるURLの処理はあまり正確ではない。

autoLinkの問題点

Spannable

URLが半角スペースで区切られていたり、2バイト文字以外で区切られていたりしたら正しくリンクとして拾ってもらえる。 例えばあいうえお https://android.gcreate.jp/ かきくけこというテキストであればURLの部分のみがURLとして識別される。 しかしあいうえおhttps://android.gcreate.jp/だとリンクを拾ってくれない。 またあいうえお https://android.gcreate.jp/がリンクになってほしいとした場合、「がリンクになってほしい」という部分までURLとして拾われてしまう。 2バイト文字でない半角カッコで囲ってかっこで(https://android.gcreate.jp/)とした場合、閉じカッコもURLに含まれてしまう。

URLの抽出がうまくいかない場合があるのが最大の問題であるが、URLのハンドリングをカスタマイズできないのもちょっと不便である。 例えばChromeカスタムタブでリンクを開きたい場合に、autoLinkでは対応できない。 autoLinkの場合、リンクをクリックするとACTION_VIEWの暗黙的インテントが発行される。

自前で処理する

以上の問題点を回避するには、自分でテキストにClickableSpanを設定してやると良い。

  1. テキストをSpannableStringに変換する
  2. テキストから正規表現を利用してURLを抽出する
  3. 抽出したURLを用いてSpannableStringにClickableSpanを設定する
  4. TextViewにSpannableStringをsetTextで設定する
  5. TextViewにsetMovementMethodを設定する
以上の手順で独自のClickableSpanを設定することができる。

コード的にはこんな感じ(Kotlinとandroid-ktxを利用している)。

val text = "あいうえおhttps://android.gcreate.jp/かきくけこ"
val spannable = text.toSpannable()
val matcher = Pattern.compile("(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]").matcher(text)
while (matcher.find()) {
  val url = matcher.group()
  val start = matcher.start()
  val end = matcher.end()
  spannable.setSpan(MyUrlSpan(url), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
textView.text = spannable
textView.movementMethod = LinkMovementMethod.getInstance()

MyUrlSpanは自分で定義する。 といっても大したことはやっていない。

class MyUrlSpan(val url: String) : ClickableSpan() {
    override fun onClick(view: View) {
        Snackbar.make(view, "$url clicked", Snackbar.LENGTH_SHORT).show()
    }
}

SnackbarでURLを表示しているだけ。 リンクをクリックした際の挙動はこのonClickでカスタマイズできる。

Read full post gblog_arrow_right

VSCodeVimのundo設定

Visual Studio Code+Vimプラグインを使っていて、undoを使った際に想定より大きくundoされてしまって使いづらい。

VimプラグインにおけるuCtrl+rによるundo/redoを、Visual Studio Codeのundo/redoにマッピングすることで対策できる。

https://github.com/VSCodeVim/Vim/issues/1490#issuecomment-352167221

User Settingsで"vim.otherModesKeyBindingsNonRecursive"を追加して、上記コメントにある設定を加えれば解決する。

babelのセットアップ覚書

JavaScriptのモジュールシステムが全く理解できなくて困っていたのだが、ようやく友だちになれそうな程度にはわかるようになった。

React Nativeやるときにはimportとかexportを使うのに、Node.jsのときはrequireになるのが解せない。 JavaScriptやるのにこいつらは避けて通れないものなのに、そもそもモジュールの仕組みもわからなくて往生した。 歴史的経緯でそうなっているのはわかったけれども、これからJavaScriptやるならどっちかにまとめたいところ。

今からやるなら新しい構文のimportで統一したいが、Node.jsはimportはそのままでは理解してくれない。 そこでトランスパイルが必要になる。

で、トランスパイルが必要なのもわかったけれども、ではどうやって準備すればいいのかがわからなくてこれまたハマる。 このあたりのセットアップがよくわからなくてJavaScript敬遠していたのもある。

ようやくbabelのセットアップのやり方、意味がわかったので覚書として残しておく。 とりあえずこれでJavaScriptであれこれ気楽に試せるようになった。

セットアップ

Node.jsのインストールとかは割愛。 nodeとかnpmは使える状態での手順。

  1. `npm init`でnode moduleのセットアップ
  2. babelのインストール`npm i --save-dev @babel/core @babel/cli @babel/node @babel/preset-env`
  3. `.babelrc`の設定
  4. `npx babel-node xxx.js`でxxx.jsをbabelでトランスパイルした上でnodeで実行
勉強のためにES6で書いたJavaScriptをとりあえずnodeで動かして動作確認できるようにするだけの目的での話。 実際にプロダクトとして動かす場合は`babel-node`は使わない。

https://babeljs.io/docs/usage/cli/#babel-node

補足

babelのインストールはcore, cli, node, preset-envがあればとりあえずは足りた。

とりあえずnpm i -gは使わない。 babelのバージョンをプロジェクトごとに管理するかどうかはわからないけれども、グローバルにインストールしなくてもnpxを使えばちゃんと動かせるので問題ない。

@babel/coreなどの@babelの部分はscoped packageといって、別にアノテーションではなくnpmのユーザ名である。 npmの仕組み上パッケージ名が早いもの勝ちになってしまうので、例えば私がbabel-some-great-utilみたいな名前でパッケージを上げたりできてしまう。 紛らわしいし、使いたいパッケージ名が使えないなんて問題が起こるので、その対策として生まれた仕組み。

@babel/coreなどはbabelのバージョン7になる。

今回はとりあえず動けばいいので、.babelrcはこれだけで問題ない。

{
  "presets": ["@babel/preset-env"]
}

本来なら動作環境に合わせて設定すべき。

https://babeljs.io/docs/usage/babelrc/

.babelrcではなくpackage.json内に"babel"ブロックを定義することでも代用できる。 この設定ならpackage.json内に書いても問題ない気もする。

スクリプトの実行はnpx babel-node xxx.jsで、トランスパイル→nodeで実行をひとまとめにできる。

ファイルの監視

さらにJSファイルを書き換えて都度コマンドを叩くのは面倒くさいので、変更を監視して自動的に再実行させる。

npm i --save-dev nodemonでnodemonをインストールする。 npx babel-node xxx.jsnpx nodemon xxx.js --exec babel-nodeに置き換えて実行する。 これだけ。

Read full post gblog_arrow_right

shields.ioを使ってバッジを表示する

https://shields.io/を使ってバッジを表示する。

今回はDart Packagesで公開されているパッケージのバージョンを表示するバッジを出したかった。 Dart Packagesはshields.ioでサポートされており、https://img.shields.io/pub/v/box2d.svgというような感じでパッケージのバージョンを表示することができる。 ちなみにbox2dの部分がパッケージ名なので、ここを自分が表示したいパッケージ名に書き換えればよい。

この方法で表示されるパッケージのバージョンは、安定版の最新バージョンが表示される。(安定版というのは1.0.0-rc1みたいにセマンティックバージョンの後ろに何もつかないやつのことを指している)

https://img.shields.io/pub/v/box2d.svg box2d

https://img.shields.io/pub/v/pointycastle.svg pointycastle

本題

ところでDartはバージョン2への移行時期であり、Flutterで利用する場合には開発版を利用する必要があったりする。 例えばredux_persistpointycastleなどがそう。 ではこれら開発版のバージョンのバッジを表示するためにはどうしたら良いのだろうというのが今回の本題。

今回はPointy Castleというライブラリの開発版バージョンを表示する。 これを書いている時点での安定版は0.11.1で開発版は1.0.0-rc1となっている。

Dart Packagesでは各パッケージのバージョン情報をjsonで取得することができる。 https://github.com/PointyCastle/pointycastleの最後に.jsonとすればよい。 shields.ioではJSONからクエリを使って直接バッジを作成することができる。https://shields.io/#dynamic-badge クエリはjsonpathを使ったクエリが使える。 今回の場合使うクエリは$.versions[?(@=="1.0.0-rc1")]になる。

https://img.shields.io/badge/dynamic/json.svg?label=pub&url=https%3A%2F%2Fpub.dartlang.org%2Fpackages%2Fpointycastle.json&query=%24.versions%5B%3F(%40%3D%3D%221.0.0-rc1%22)%5D pub

ちなみにこの方法では動的に最新バージョンを取得することはできない。 (クエリをうまいこと扱えばできるのかもしれないが、私にはわからなかった) 例えばクエリを$.versions[?(@.includes("rc"))]とするとバージョンにrcを含むものだけが取得できる。 しかしrcを含むバージョンが複数ある場合、それら全てが列挙されてしまう。

$.versions[?(@.includes("+"))]をクエリに使った場合: +versions

FlutterでBottomAppBarを表示

サンプルはここを確認するとよいと思う。

https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/demo/material/bottom_app_bar_demo.dart

このサンプルではFABの位置やノッチの有無などをカスタマイズできるようにしている関係でなんだかややこしそうに見えるが、やることは非常に単純。

  • Scaffold内のbottomNavigationBarにBottomAppBarを渡す
  • Scaffold内のfloatingActionButtonLocationでFABの位置を指定
  • ノッチの有無はBottomAppBarのhasNotchで指定
  • 必要なメニュー等はBottomAppBar内のchildに追加

これだけでBottomAppBarを表示できる。非常に簡単。 BottomAppBar内のアイテムを複数指定したい場合、childなのでRowでくるむなどの工夫が必要。

flutterの公式リポジトリをクローンしてきて、material demoを動かすとどんな動きか確認することができる。

ちなみに公式リポジトリ内のexampleを動かすにはここを見ればよい。

flutterの初期設定を済ませていることを前提として、手っ取り早くexampleを実行するために必要なことをまとめるとこうなる。

  • クローンしたあとbinディレクトリにパスを通す(リポジトリ内の最新版flutterを使うため)
  • `flutter update-packages`を実行して各種依存パッケージの解決を行う
  • IntelliJ IDEAを使っているなら`flutter ide-config --overwrite`を実行することでRun Configurations等の設定をしてくれる
  • 後は実行したいexampleをIDEから選んで実行

binディレクトリにパスを通すと、普段使っているflutter(私の環境だと0.3.2)と公式リポジトリのflutter(これ書いてる時点では0.4.4-pre.8)が混じってしまうのだけど、公式リポジトリを触っているときだけ後者を有効にするみたいな設定の仕方ってあるのだろうか。 ありそうな気がするけど私は知らないので、都度パスを追加して対応している。

追記

別にリポジトリをクローンしてこなくてもPlayストアにアプリが公開されていることを知った。 https://play.google.com/store/apps/details?id=io.flutter.demo.gallery 動きを確認するだけならこっちのほうが手っ取り早い。

Settings Repositoryプラグインを使ってIDEの設定を共有する

いつもOverwrite local/Overwrite remoteが、どっちがどっちなんだっけと使うときに混乱するのでメモ。

どっちがどっち、というのはovwerwrite localが、現在の設定をリモートリポジトリの設定で上書きするのか、現在の設定でリモートの設定を上書きするのか混乱してしまうのである。 Overwrite localは現在の設定をリモートの設定で上書きする、が結論なんだけど。 これがわかりにくいと思うのは私の英語力がないせいなのだろうか。

Settings repositoryの設定について

ちなみにSettings Repositoryをご存じない方向けに簡単に紹介。

IntelliJ IDEAで使える設定共有用のプラグイン。 私の場合で言うと、Android Studio(stable/canary)とIntelliJ IDEA community editionを行ったり来たりすることがあるので、このプラグインを使って設定を共有している。 バージョンアップする際に以前の設定を引き継ぐというのはできるが、同時に運用しているときにAで行った設定変更をBでもまたやらないといけない、というのが起こらなくなるので非常に便利だと思う。

利用しているプラグインによっては、そのプラグインの設定ファイルもバックアップ対象になるらしい。 例えば私はIdeaVimを利用しているが、どのファイルのどの位置にカーソルが移動した、という情報までバックアップ対象になってしまっている。 そのため必要に応じて不要な設定ファイルは.gitignoreで管理対象から外すなど工夫が必要。 (私はvim_settings.xmlは管理対象から外している)

MacであればFileメニューのところにSettings Repositoryがあるので、GitHubなりBitbucketなりで管理用リポジトリを用意してそのURLを設定すれば使えるようになる。

ちなみにどうもエディタでタブを使わないとか、キーマップの設定でどれを使うかなどまでは合わせてくれないようだ。 例えば私の使っているキーマップはMac OS X 10.5+ copyとなっているのだが、このキーマップ設定自体は共有してくれるものの、これが有効な状態にまでは復元してくれない。 そのため、どのキーマップを使うか、コードスタイルはどれを使うかなどは手動で直さないといけないようだ。

JavaScript再入門中

JavaScriptはやろうかなと手を出した時期があったのだが、そのときは挫折した。挫折したというか、優先度の問題から深入りするのを止めたというのが正しい。

当時はAndroidのコーディングすらまともにできていないのに、JavaScriptに手を出すのは無謀ではないかと言い訳をしていた記憶がある。

Read full post gblog_arrow_right