20191002のAndroidに関する記事は6件です。

BindingAdapterでTextViewのdrawable[Left/Right/Top/Bottom]を実現する

自分用のメモも兼ねて。

DataBindingを使ってTextViewのdrawbleLeft(Start)を使うために
下記のような指定すると、実は上手く行きません。

<layout>
    <data>
        <variable name="icon" type="int" />
    </data>
    <TextView
        android:drawableLeft="@{icon}"
    />
</layout>

なぜなら、 この指定をするとcolorリソースとして認識されてしまうからなんですね。
DataBindingではIntergerとしてresource指定をすると勝手にcolorResourceとして認識されてしまうようです。

attrの定義を見れば分かりますが、

<attr name="drawableLeft" format="reference|color" />

こうなっています。

なので、

@BindingAdapter("drawableLeftFromResource")
fun drawableStartFromResource(textView: TextView, @DrawableRes res: Int) {
    val drawables = textView.compoundDrawablesRelative
    textView.setCompoundDrawables(
        ContextCompat.getDrawable(textView.context, res)?.apply {
            setBounds(0, 0, intrinsicWidth, intrinsicHeight)
        },
        drawables[1],
        drawables[2],
        drawables[3]
    )
}

のようなBindingAdapterを定義して、

<layout>
    <data>
        <variable name="icon" type="int" />
    </data>
    <TextView
        android:drawableLeftFromResource="@{icon}"
    />
</layout>

としてやると上手くいきます。

肝は

ContextCompat.getDrawable(textView.context, res)?.apply {
            setBounds(0, 0, intrinsicWidth, intrinsicHeight)
        }

の部分で、setBoundsをしてやらないとiconを差し込むための余白ができないので、アイコンが表示されません。

同じ要領でRight、Top、Bottomにも適用可能です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

2週間でGAE+Datastore+MonacaによるiOS,Android,Webサービスをリリースするまでにやったこと

はじめに

Web初心者なので大変おこがましいと思いながらも、サービスをリリースするまでの過程について、備忘録も兼ねて記載していきたいと思います。 ポートフォリオも兼ねているため、今回収益化は度外視しているので、広告も出していません。次回以降は収益化も考慮して、より使ってもらえるサービスを作成していきたいと思います。

noteにもリリースするまでの話を記載しようと思っていますが、Qiitaではリリースするまでの技術的な内容を記載していこうと思っています。クラウドの発展やフレームワークの充実化が進み、便利な時代になっているもので、特にここではWebの初心者が2週間でサービスをリリースするまでやったことをフェーズ別に自分の経験を踏まえて備忘録として残しておきたいと思います。

先に使用したライブラリ・技術について記載をしておくと、

Webサイト

  • サーバー/PaaS(Google App Engine)
  • データベース(Datastore/Firestore)
  • CSSフレームワーク(Bootstrap)
  • サーバーサイド(Python/Flask(これは何でもよい!))

アプリ開発(Android/iOS)

  • Monaca

が今回の開発環境で、個人的には簡単なのでおすすめの組み合わせです。(Webサイトに関しては、Firebaseでも良いかもしれませんが。)

特に後述しますが、Monacaはモバイルアプリ開発の初学者に超おすすめです。 Cordovaベースのハイブリッドアプリ(iOSでもAndroidでもどこでも動くアプリ)作成ツールなのですが、クラウドIDEがあり、開発もビルドもクラウド環境で出来るため、ローカルでの環境構築が不要です。 つまり、Android StudioやXcodeを使ってiOSのSimulatorの環境を設定しなくても、クラウド上でポチポチするだけで簡単なデバッグや実機を用いた動作検証が可能です。(Cordovaベースなので少し遅いのがネックですが。)

対象読者

  • ワタシハフロントエンド(もしくはバックエンド)チョットダケデキルという人

逆にいうと、現役でソフトウェア開発に携わっている方にはありきたりな話になってしまうかもしれません!

サービス作成費用

サービス関連

  • ロゴ作成: $9 (約970円)
  • お名前.com(年間): 1円

その他の費用としては、

  • Chrome ウェブストア登録費用(初回): $5 (約540円)
  • Google プレイストア登録費用(初回): $25 (約2,700円)
  • Apple Store登録費用(年間): $99 (約10,700円)

Webサービスはほぼデザイン代だけで1,000円以下程度の費用で、App Store、Google Play Store、Chromeウェブストアの登録費用込みでも15,000円でお釣りが来る程度です。 更にその後開発したアプリやサービスは登録費用がかかりませんので、サービスを作ってみたい方は安い投資だと思って、登録してみるのも悪くないかと思います。

作成したサービス

3つの単語の組み合わせで、特定のURLにリダイレクトできるSantangoというサービスを作成しました。
what3wordsというイギリスのスタートアップのサービスのアイデアとbit.lyやGoogle URL Shorter(サービス終了済)のようなURL短縮サービスのアイデアを組み合わせて、3単語で任意のURLに飛ぶ事が出来るサービスです。

(web_top.png
▲作成したサービスの画面

技術的な面での機能としては以下の二点のみなので、簡単なサービスになっています。

  • Register URL
    • あるURLを登録して、特定の3単語を生成
  • Santango
    • URLから生成された3単語を用いて、元のURLにリダイレクト

具体的なユースケースとしては、セミナーやカンファレンスの登壇時に、スライドや資料をシェアする事を想定しています。通常だとQRコードか複雑なURLをシェアする必要がありますが、Santangoを介することで3単語だけシェアすればスライドや資料にアクセスすることが出来ます。 他にも、Qiitaのプライベート投稿のURLを口頭で伝える場面や大勢にまとめてシェアする場面などいくつかユースケースは考えられると思います。 特にエンジニアのように勉強会がある文化圏では便利な状況があると思うので、ぜひ使って頂けると幸いです。

ちなみにこの画面からリダイレクトしなくてもブラウザの検索バーに登録したり、Chrome Extensionやモバイル版(iOS/Andoroid)を使用することでより簡単にURLに飛ぶことが出来ます。

各フェーズについて

  • 企画フェーズ

    • 競合調査
    • スケッチ・ワイヤーフレーム・モックアップの作成
  • デザインフェーズ

    • CSSフレームワーク選定
    • ロゴ・テーマ作成
  • 開発フェーズ

    • 技術選定
    • Webサービス
    • バックエンド開発
      • Flask + GAE + Datastore
    • フロントエンド開発
      • CSS + JavaScript
    • ドメイン取得
    • Chrome Extension
      • JavaScript
    • Android/iOS
      • Monaca(Cordova-based)
  • 機能改善・運用フェーズ

    • パフォーマンスの高速化
    • アルゴリズムの改善
    • テスト
    • Google Analyticsの導入
  • その他

    • ドキュメンテーション
    • タスク管理
    • 宣伝
      • 広告動画作成

個人開発だとやる事が沢山あり、チームで進めるお気持ちが分かるようになります。実際の開発としては柔軟に別のフェーズに行き来しながら開発を進めていました。 ストア用マテリアルの作成やプライバシー規約など地道+面倒な作業のときは、Netflixを見ながら奇声を上げたり一人チルしたりゆるゆると進めていました。

企画フェーズ

競合調査

それでは早速プロダクトを作成していく...前に、簡単に競合調査を行いました。

  • bit.ly
    • URLの短縮サービス大手
    • 有償サービスあり
    • ランダム文字列ではない省略URLの提供
    • コンテンツのトラッキング
  • Google短縮サービス(goo.gl)
    • こちらも短縮サービス大手だけど、終了済。
    • 短縮サービスによるメリットが少なかった?
  • What3words
    • 住所を3単語で特定出来るサービスのBtoBビジネスモデル
    • ツバルやモンゴルの企業の一部では採用されている

今回作成したサービスは短縮サービスのデメリットの一つである、「URLが分かりづらい」という点を3単語で表現することで、「わかりやすい/覚えやすい」形でURLを表現するようにしました。ただし、ある程度短縮サービスについて詳しい方はお気づきかと思いますが、3単語で表現できるようになったからといって、URL短縮サービスの元々のデメリットの一つであるセキュリティ的な観点は今回のサービスでも同様に存在しますので、一つの懸念事項としてはありました。リダイレクト前にURL先を一時的に表示するなどして、今後の改善ポイントもあるかと思います。(他の短縮サービスではなぜやっていなかったのだろう。)

今回のサービスでは競合調査の深堀りをしていませんが、実際プロダクトを作成するにはどのようなビジネスモデルで収益を上げていくか、他サービスのポジションとの差別化を明確にしていくと良いかもしれません。

スケッチ・ワイヤーフレーム・モックアップの作成

次に実装する前のスケッチ・ワイヤーフレーム・モックアップに関してです。(ちなみに、それぞれの用語の細かい違いについてはこちらが参考になりそうです。)

まず、作成するサービスのイメージ図をiPadのGoodNotesというアプリで記述していきました。単純なメモ書きなので、紙でも良いと思います。

今回の場合は、

  1. スケッチ作成
  2. サーバーサイド実装(簡素な画面)
  3. モックアップ作成(BootstrapのThemeをダウンロードしてカスタム)
  4. 実装に乗せる

という流れで取り掛かりました。

ちなみに、今回はワイヤーフレームの作成をしなかったので使用しませんでしたが、もう少し複雑なアプリやサービスであれば、OverflowProttのようなサービスを使うと便利かもしれません。どちらもコード不要で、アプリやWebサービスの画面遷移図を記述して、ワイヤーフレームやモックアップを作成する事が出来るツールです。また、今回はスプラッシュ画面制作用にしか用いていませんが、Adobe XDでワイヤーフレームを作成していくことも可能です。

デザインフェーズ

CSSフレームワーク選定

こちらはだいぶ人の好みによって分かれるかと思いますが、私の場合は結局Bootstrapに落ち着きました。

開発着手当初は脱Bootstrapと意気込んで、別のフレームワークを使っていたのですが、細かい箇所でストレスも多く、ネットでも情報が一番多く実装が手軽だったので、Bootstrapにしました。テーマを変更する事でだいぶマシになるかと思いますし、資金に余裕がある方は4-5,000円なので有料版のテーマを購入しても良いかと思います。

ちなみに私が今回使用したテーマはこちらです。

ロゴ・テーマ作成

ロゴはLOGASTERというサイトを使いました。他にも良いサービスはいくつかあるかもしれませんが、$9 (約970円)なのでそれなりにお得な値段だと思います。こちらのデザインをベースにWebサイトのfavicon.ico、Chrome Exntesionの拡張機能のアイコン、iOS/Androidのアイコンを作成していきました。(私の環境はMacです)

Webサイト

  • favicon.ico(16x16)
    • 加工→プレビュー→ツール→サイズ変更→保存
  • トップページ
    • (加工なし)

Chrome Extensionの拡張機能

  • アイコン各所用
    • 16x16, 19x19, 32x32, 38x38, 48x48, 64x64, 128x128

iOS

  • スプラッシュ画面作成

    • Adobe XDでそれぞれのサイズを作成
    端末 サイズ
    iPhone 320x480
    iPhone Retina 640x960
    iPhone 5 640x1136
    iPhone 6 750x1334
    iPhone 6 Plus 1242x2208
    iPhone 6 Plus(Landscape) 2208x1242
    iPad 768x1024
    iPad(Landscape) 1024x768
    iPad Retina 1536x2048
    iPad Retina(Landscape) 2048x1536
  • アプリストア登録用アイコン

    • アルファチャンネルを削除しておく必要があるため、プレビュー→エクスポート→アルファチェックを外して保存

adobe.png

▲Adobe XDで作成したスプラッシュ画面

また、自分はデザインセンスなど皆無なのですが、UIに関してもサービス開発の上では重要だと考えています。CSS配色パターンはベストプラクティスがあるので、過去の賢人の知恵をお借りして配色を決定していきます。colorkittycolordropcolorhuntなど沢山配色パターンのサイトはありますが、私はcolorhuntを参考にして全体の配色を決めていきました。

また、サイト内の画像などに関しては、unsplashというフリーで商用可能な画像が多く集まっているサイトがあるので、こちらから画像を何点かお借りして少し編集を加えて載せています。特に今回のサービスでは緑などの自然系の色を用いて、開発を進めていきました。

開発フェーズ

技術選定

今回はポートフォリオということもあり、あまり深くは考えていませんでしたが、簡単かつなるべく費用はかけずにある程度はスケールできるような技術を選定していく必要がありました。なるべくミニマムなフレームワークで良いと思っていたので、サーバー側に関しては以下の技術を選びました。また、上述したように機能が簡素なものになっており、更にChrome Extensionもアプリ開発も実は中身は似たような内容になっているので、どちらも素のJavaScriptで記述しています。

サーバーサイド

  • Flask(PythonのミニマムなWebフレームワーク)
  • Google App Engine(GAE)
  • Datastore
  • CSS + Javascript

アプリ開発(Android/iOS)

  • Monaca(Cordova-based)

Chrome Extension

  • JavaScript

特にGAE、Datastore、Monacaがめちゃ便利です。

Webサービス

バックエンド開発

  • Flask
  • GAE
  • DataStore

を用いて作成を行いました。 詳しい実装方法は長くなりそうなので避けますが、大まかには以下のような流れで進めました。

  1. flaskでゴニョゴニョ開発
  2. ローカル環境での動作が確認できたら、gcloudを使って開発環境にデプロイ
  3. 開発環境での動作が確認できたら、gcloudを使って本番環境にデプロイ

個人的にGAEが便利だと思っている事の一つですが、バージョン名を変更する事で、簡易的に開発環境と本番環境を切り分けて動作を確認する事が出来ます。本番時に何か障害が発生した場合には、手軽に以前のバージョンに戻す事が可能です。また、アクティブなトラフィックを分割する事も可能なので、ABテストも簡単に行う事ができます。GAE、DataStoreのメリットとしては、そこそこスケールが出来て、ある程度のトラフィックまではほぼ無料で使用する事がかなり大きいです。例えば、Datastoreでは1日に書き込み20,000件、読み込み50,000件呼ばれても無料枠に収まりますし、それ以上アクセスが来ても個人レベルだとそこまで費用は掛かりません。便利。

フロントエンド開発

そこまで複雑になる予定がなかったため、フロントエンド箇所はバックエンドより更にシンプルにコーディングしています。素のJavaScriptでのみ記載しており、オールインワンでスクリプトにて記述しているため、かなり乱雑です。

ここではChrome Developer Toolsを用いて、CSS / JavaScriptを記述していきます。そこまで複雑な処理を記載していませんが、以下のような実装、確認を行っています。

  • CSSデザインの確認
    • 特にレスポンシブが動作しているかどうか
    • 様々な機種でWebサイトを確認したときに、ずれないようになっているか
  • Javascriptの内容の確認
    • ここでは若干のアニメーションの動作確認

今回のようなシンプルな機能のサービスだとそこまで記述する内容が少なかったですが、実際はフレームワークを使用する事が多いです。

ドメイン取得

こちらもGAEの良い点ですが、

  • SSLの証明書が自動で発行してくれるマネージドSSLがある(さらにSSLエンドポイントが世界中にあり、負荷分散してくれる)
  • カスタムドメインを登録するだけで簡単に使用できる

とメリットが多く簡単なので、初学者でも簡単に作成することが出来ます。

ちなみに自分はお名前.comでドメインを取得しましたが、ドメイン取得サイトはどこでも良いと思います。Google Domainだとより手軽に連携できるようです。

取得したドメインをこちらの手順に従って登録していけば完了です。

Chrome Extension

今回のアプリでは特にChrome Extensionが便利になるかと思い作成しました。Chromeの拡張機能は作成方法や公式サンプルも豊富だし初回の登録費用もたったの$5なので、アプリ開発よりかなり敷居が低く、初心者でも参入しやすいと思います。また、個人的に感じたのは、App StoreやPlay Storeと違い、Chromeのウェブストアではマテリアルが簡素な事が多かったです。

ちなみに、Chrome Extensionの中で使用しているXMLHttpRequestという仕組みをiOS/Andoroidでも同じように使用しています。

Chrome拡張機能の作成方法は

  1. manifest.jsonの記述
  2. instructionの追加・記述(ここではbackground.js)
  3. ユーザーインターフェースの追加(ここではpopup.html)
  4. アイコン・マテリアルをせこせこ作成
  5. Chromeウェブストアに登録(更新)

という流れになっています。

popup.png
▲ユーザーインターフェース(popup.html)

webstore.png
▲下部分の画像が作成したマテリアル

ちなみにマテリアルの作成には、Powerpointを使用しました。かなりの少数派だと思いますが、Powerpointを使用したことがある方は意外と簡単に作成出来るかと思います。ちなみに今回のChromeウェブストア・Googleプレイストア・App Storeそれぞれのストア用画像を作成しました。

Android/iOS

アプリ開発ではMonacaと呼ばれるハイブリッドアプリ開発プラットフォームを使用しました。一度作成してしまえば、Android/iOSでも似たような挙動で動作することが可能です。他のハイブリッドアプリ開発手法やネイティブアプリに比べると挙動は遅いという点はありますが、初学者でも簡単にアプリを作成する事が出来ます。また、React、Vue、Angularなども対応をしているため、順当に学んでいけば、より凝ったアプリを作成することも可能です。(ただし、MonacaやOnsenUI特有の挙動もありますが)

Monacaの主なメリットとしては以下のようになります。

  • Android/iOSそれぞれの開発環境を整備しなくても、クラウド上で開発・ビルドが可能
  • 実機でのデバッグが簡単に可能
  • 1日のビルド上限数があるが、無料プランでもある程度使える

機能改善・運用フェーズ

パフォーマンスの高速化

この時点でそこまで気にする必要はないのですが、PageSpeed InsightsというGoogle製のパフォーマンス測定を行ってくれるツールがあります. こちらでパフォーマンス測定を行ってみましょう。

speed.png
▲Pageinsightのスコアの画面

このようにある程度のスコアが出ると、アクセスの速度としては問題ないかと思います。

そして、パフォーマンス測定後のアクションとして例えば以下のようなものがあります

  • 使用していないCSS/JSの削除
  • 大きい画像を圧縮

使用していないCSS/JSの削除に関しては、UnusedCSSPurifyCSSという使用していないCSS箇所を削除してくれるサービスがあります。私が今回使用したのはPurfiyCSSをオンラインで実行してくれるPurifyCSS Onlineというサービスを使用しました。

purify.png
▲PurifyCSS Onlineの画面

今回のプロダクトではCSSのボリュームがそこまで大きくなかったのですが、もっと大きいプロジェクトの場合は変化が出てくるかと思います。

また、よしなに画像を圧縮してくれるGoogleのSquooshというサービスでは、ブラウザ上で画像をドラッグアンドドロップ+簡単な操作で、軽い画像を生成してくれるので、元画像と置き換えておくと便利です。

squoosh.png
▲Squoosh

特に画像の場合はCSSより大きいので、先程行った無駄なCSSの削除より画像の圧縮の方が効果が大きい事が多いです。

また、一つ気をつけないといけない点として、GAEのスピンアップ時のパフォーマンス測定と通常時のパフォーマンス測定を切り分けて考慮する点があります。

GAEは基本的に安く運用するために常時インスタンスの起動をしておらず、一定時間アクセスがないとインスタンスをシャットダウンします。これをスピンダウンと言います。 これに対して、アクセスがあった際に、インスタンスを立ち上げてリクエストの処理を行います。これをスピンアップと言います。

そのため、パフォーマンス測定時にスピンアップの問題なのか、サーバーそのものの問題なのか切り分けて効果を測定する事が必要です。

アルゴリズムの改善

今回作成したサービスでは「いかに覚えやすいか」が肝にもなっているので、アルゴリズムの工夫やデータの前処理に関しての工夫は重要になっています。詳細については割愛しますが、以下のような流れで作成していました。

  1. データセットの取得
  2. 前処理
  3. 覚えやすい単語の組み合わせ生成

使用しているデータは単語の羅列データなので、今回は簡易的に品詞を付与して、文法を入れたルールベースによる単語の組み合わせ生成を行っています。 発展的には機械学習を応用することで「覚えやすい単語の組み合わせの生成」などを行う事が出来ます。しかし、URLの登録の際、単語の組み合わせを生成するリアルタイム処理が必要なため、URL生成のバッチ処理+プリフェッチ処理を加えるなどの工夫をする必要があるため、少しコストが高いです。そのため、重要な箇所ですが、現状は簡易的なアルゴリズムをリアルタイムで動作するようにしています。

テスト

基本的にはテストを書きつつ実装を進めていった方が良いし、いつでもテストを書く癖をつける事は重要だと思っていますが、プロトタイピングの中ではテストコードをガッツリ書いたり、CIの環境を整備するよりも、他に優先すべき点があるかと思っています。個人的には抑えるべきテストをチェック出来ていれば良いかと思っています。ここらへんは経験の浅さから認識が甘い点があるかもしれませんので、あまり参考にはならないかもしれません。

そのため、今回のプロダクトではテストに関してそこまで行わず、以下の確認を行っています。

  • 単体テストの記述
  • 最低限チェックしたいテストケースのドキュメンテーションと実行
    • 自動化せずに、開発環境デプロイ後に手動でチェック

testcase.png
▲テストケースのイメージ

Google Analyticsの導入

基本的に導入しておくと便利なので早めに入れておいて良かったです。細かく目標やコンバージョンの設定なども出来るのですが、単純なアクセス解析程度の用途でも個人プロダクトだと十分活用することが出来ます。

例えば、

  • ユーザーの国別情報(どこの国からアクセスがあるか)
  • デバイス情報(スマホかタブレットかPCか)
  • どこのページから飛んできたか
  • どの時間帯にアクセスが来ているか

といった基本的な情報は設定しなくても取得することが出来ます。

その他フェーズ

ドキュメンテーション

自分の性格的に得意ではないのですが、GAEのバージョン管理のログ、参考になったURL、テストケース、やったことの概要などに関して、ドキュメンテーションを残しておく事は重要で、後々見返してきたときに便利です。

私は開発やコンペなどの個人的なプロジェクトはNotionというツールを使っています。ちなみにNotionの容量は限界がないので、動画などをドキュメンテーションの一部に入れておいても全く問題ないので、便利です。

project.png

▲Notionのプロジェクトページ内の目次一覧

タスク管理

開発プロダクトやコンペごとにTrelloのボードを切って、一人プロジェクトを進めていました。 全プロジェクト横断で確認したいときがあるので、他のツールやプラグインなど知見がある方は教えて頂けると助かります。

trello.png
▲Trelloボード

宣伝

今回は特に行っていませんが、通常であればこの後広告を打ったり、SEO対策としてコンテンツを増していったり、とやり方は色々ありそうです。 私はあまり詳しくありませんが、Google Search Consoleなどを使ったSEO施策を行っていく事で、広告よりもコスパ良くコンバージョンにつなげていく事が多いそうです。

また、開発会議SafariServiceといったサービスを紹介していただけるサイトも豊富なので、登録してみても損はないと思います。 このあたりのTODOに関しては、こちらのブログのリリース後にやったことリストが参考になりました。

また、私の場合は英語のサービスを作成していたので、Y Combinatorが支援をしているProduct Huntにも登録をしてみました。もしサービスを気に入っていただけた方はupvoteしていただけると幸いです!

動画作成

また、結局非公開にしましたが、Youtube動画を作成して、トップページに配置するのも一つの手かと思います。(どちらかというとデザインに近い話ですが) Youtubeに宣伝動画があると、Webのトップページ、Chromeウェブストア、Google Playストア、App Storeに乗せる事が出来ます。

勿論動画作成ツールなどを使用するのも良いと思いますが、個人開発レベルであれば、以下のサービスを組み合わせて作成するのが簡単かと思います。

  • サービスのスクリーンショット
    • Quick Time Player
  • 動画編集
    • iMovie
    • Youtube動画エディタ
  • アニメーション機能
    • Powerpoint

まとめ

サービス開発従事者の方(特にエンジニア)にはありきたりの内容が多かったかと思いますが、自分の中での整理も兼ねて個人サービス開発の工程を書いてみました。初学者なので、何か追加した方が良い事柄や、より良いツールなどアドバイス頂けると幸いです!

本記事ではフェーズごとに行ったことをまとめてみました。個人開発の備忘録としては、便利なサービスが豊富な時代なので、思っているより簡単に個人開発を行う事が出来ます。登録費用を別にしたら、費用としても1,000円ぐらいなので、個人プロダクト開発に手を出してみたいと思っている方はぜひ挑戦してみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidスマホでFORTRAN IV

概要

AndroidスマートフォンにLinux環境を構築し、FORTRANプログラムを走らせるためのセットアップについて説明します。

Android OSのバージョンは9ですが、たぶんtermuxが動作してその上でCコンパイラが動けばほかのバージョンでもOKのはずです。

はじめに

長年のガラケーユーザだった私のところにもスマホがやってきました。

回路設計や開発をやっていますが、最近の電子工作はスマホやタブレットが絡むモノが多い(LineThingsとか)のにスマホの端末がないので実験するときは大騒ぎ!! 職場で端末持ってるやつを捕まえて実験台にするという荒業でしのいでましたが、ついに上司が実験用として端末を用意してくれました(TNX)。

操作に慣れないとだめだから月額基本料金の枠内で自由に使ってくれてよいということなのですが、さあ何しよう....

そうだ、プログラム書こう!!

今時の人だったらPythonとかそうしたものをやると思いますが、CとBASICとFORTRANとアセンブラしか知らないのでどうしたものか....

そうだ、FORTRANで書こう!!

FortranじゃなくてFORTRAN。古のあのFORTRAN IVのプログラムを最新Androidスマホでやろう....

かくして悪戦苦闘の結果何とかFORTRAN IVのプログラムを動かすことに成功したので次の通り報告します。

FORTRAN 貴方のお家は何処?
私のお家はFACOMなのよ やさしいコンパイラのひとつなのよ
(FACOM FORTRANのマニュアルより)

手順

termuxのインストール

まずはAndroidスマホ上にLinux環境を構築します。最初はCCToolsにしようと思っていたのですが、いつの間にかGoogle Playストアから消えてなくなっていたので、課金なしで使えるtermuxをインストールしました。

Google Playストアで探すとすぐ見つかり、ワンタッチでインストールできます。

すまほ、べんり!! (ガラケーユーザなのでとても感動する)

Cコンパイラなどのインストール

インストールしたtermux上でCコンパイラをインストールします。

Linux上のCコンパイラといえばgcc、FORTRANコンパイラといえばgfortranなのですが、NDKが最近全面的にclangベースに切り替わってしまったらしくgcc系が動かないみたいなので、clangをインストールします。

termux $ pkg install clang
termux $ pkg install make

必要かどうかわかりませんがits-pointlessもインストールしてみました。

termux $ wget https://its-pointless.github.io/setup-pointless-repo.sh
termux $ bash ./setup-pointless-repo.sh

f2cのソースをダウンロード

gfortranがtermux上でインストールできないみたいなので、昔懐かしのf2cをインストールすることにします。

FORTRAN(77まで)のソースをCコンパイラが食べられるソースに変換するプログラムです。ソースおよびマニュアル類は

http://netlib.org/f2c

以下にあります。

必要なのはf2cのソース(tgz)とlibf2cのソース(zip)です。下記のとおりダウンロードして展開します。

termux $ wget http://netlib.org/f2c/src.tgz
termux $ wget http://netlib.org/f2c/libf2c.zip
termux $ tar zxvf src.tgz
termux $ unzip libf2c.zip

f2cのソースをコンパイル

展開したらmakefile.uファイルをMakefileにコピーします。あとはmake一発でコンパイルできます。

コンパイルできたらtermuxのusr以下にファイルをコピーします。

f2c -> usr/bin
xsum -> usr/bin
f2c.h -> usr/include

libf2cのソースをコンパイル

こちらも展開したらmakefile.uファイルをMakefileにコピーします。

コピーしたらMakefileのCFLAGSのところに次の定義を追加します。

-DNON_UNIX_STDIO

次に、uninit.cファイルの中から"__linux__"を探して、次のinclude文をコメントアウトします。

// #include "fpu_control.h"

この1文をコメントアウトしておかないとコンパイル時にエラーになります。

準備ができたらmakeでコンパイルし、出来上がったlibf2c.aをtermuxの usr/lib 以下にコピーしておきます。

いざコンパイル

次のプログラムを書きました。なお、昔ながらのFORTRANですので固定書式じゃないとf2cから構文エラーで怒られます。

test.f
      WRITE(6,100)
  100 FORMAT(12H HELLO,WORLD)
      END

コンパイル実行は次のとおり行います。

termux $ f2c -T. test.f
termux $ cc test.c -lf2c -lm
termux $ ./a.out
 HELLO WORLD

まとめ

Androidスマホ上でFORTRAN IVプログラムを動かす方法について報告しました。

これでいつでもどこでもFORTRANプログラミングが楽しめます。最新スマホで大昔のFORTRAN IVプログラミングをやるのもなかなか楽しいものです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSONArray.map() が欲しかったので作る

はじめに

JSONArray を加工しつつ List にしたかったので、JSONArray.map() を作りました。

[追記]

  • 下記のコードより、@htsign さんのコメントのコードの方が良さげなので、もし参考にされる方がいらっしゃいましたら是非そちらを!
  • filter 等以外にも欲しくなった時に、全部用意していくと大変なので、@sdkei さんのコメントの様に List 等に変換してしまった方が良いかもしれません。

JSONArray.map()

こんな感じの拡張関数を用意して、

JsonExt.kt
fun <T> JSONArray.map(transform: (JSONObject) -> T): List<T> {
    return mutableListOf<T>().also {
        for (i in 0 until length()) it.add(transform(getJSONObject(i)))
    }
}

こんな感じで使います。

利用するコード
list = jsonArray.map {
    // TODO
    it.get("key") // value
}

JSONArray.forEach() と JSONArray.filter()

ついでに forEachfilter も用意しました。

JsonExt.kt
fun JSONArray.forEach(action: (JSONObject) -> Unit) {
    for (i in 0 until length()) action(getJSONObject(i))
}

fun JSONArray.filter(predicate: (JSONObject) -> Boolean): List<JSONObject> {
    return mutableListOf<JSONObject>().also { list ->
        for (i in 0 until length()) {
            getJSONObject(i).let { if (predicate(it)) list.add(it) }
        }
    }
}

おわりに

今回は Qiita に何か投稿してみることを目的に、記事を書いてみました。
ここ間違ってる、こっちの方が良い、等々ありましたら教えてもらえると嬉しいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

書籍「Androidアプリ開発の教科書」 の11章をRetrofit2でリファクタリング

Androidアプリ開発の教科書」の11章「非同期処理とWeb API連携」のサンプルコードを
Web API連携でよく使われているライブラリ(Retrofit2 + Gson + Okhttp)でリファクタリングしてみました。

  • Gson・・・JavaオブジェクトとJSONデータをシリアライズ/デシリアライズするライブラリ
  • okhttp・・・HTTPおよびHTTP2のネットワーククライアントのライブラリ
  • Retrofit2・・・ネットワーク通信のためのライブラリ

本書のサンプルはリストにある地域をタッチするとlivedoor天気情報のWebAPIを使って、
天気情報を表示するアプリです。
学習目的でサンプルを改造した過程を記事にします。

image.png

リファクタリング対象のコードはこちら↓
実行できる環境が欲しい方はこちらからダウンロードできます。

WeatherInfoReceiver.kt
    private inner class WeatherInfoReceiver(): AsyncTask<String, String, String>() {
        override fun doInBackground(vararg params: String): String {
            //可変長引数の1個目(インデックス0)を取得。これが都市ID
            val id = params[0]
            //都市IDを使って接続URL文字列を作成。
            val urlStr = "http://weather.livedoor.com/forecast/webservice/json/v1?city=${id}"

            //URLオブジェクトを生成。
            val url = URL(urlStr)
            //URLオブジェクトからHttpURLConnectionオブジェクトを取得。
            val con = url.openConnection() as HttpURLConnection
            //http接続メソッドを設定。
            con.requestMethod = "GET"

            //接続。
            con.connect()

            //HttpURLConnectionオブジェクトからレスポンスデータを取得。天気情報が格納されている。
            val stream = con.inputStream
            //レスポンスデータであるInputStreamオブジェクトを文字列(JSON文字列)に変換。
            val result = is2String(stream)
            //HttpURLConnectionオブジェクトを解放。
            con.disconnect()
            //InputStreamオブジェクトを解放。
            stream.close()

            //JSON文字列を返す。
            return result
        }

        override fun onPostExecute(result: String) {
            //JSON文字列からJSONObjectオブジェクトを生成。これをルートJSONオブジェクトとする。
            val rootJSON = JSONObject(result)
            //ルートJSON直下の「description」JSONオブジェクトを取得。
            val descriptionJSON = rootJSON.getJSONObject("description")
            //「description」プロパティ直下の「text」文字列(天気概況文)を取得。
            val desc = descriptionJSON.getString("text")
            //ルートJSON直下の「forecasts」JSON配列を取得。
            val forecasts = rootJSON.getJSONArray("forecasts")
            //「forecasts」JSON配列のひとつ目(インデックス0)のJSONオブジェクトを取得。
            val forecastNow = forecasts.getJSONObject(0)
            //「forecasts」ひとつ目のJSONオブジェクトから「telop」文字列(天気)を取得。
            val telop = forecastNow.getString("telop")

            //天気情報用文字列をTextViewにセット。
            val tvWeatherTelop = findViewById<TextView>(R.id.tvWeatherTelop)
            val tvWeatherDesc = findViewById<TextView>(R.id.tvWeatherDesc)
            tvWeatherTelop.text = telop
            tvWeatherDesc.text = desc
        }

        /**
         * InputStreamオブジェクトを文字列に変換するメソッド。変換文字コードはUTF-8。
         *
         * @param stream 変換対象のInputStreamオブジェクト。
         * @return 変換された文字列。
         */
        private fun is2String(stream: InputStream): String {
            val sb = StringBuilder()
            val reader = BufferedReader(InputStreamReader(stream, "UTF-8"))
            var line = reader.readLine()
            while(line != null) {
                sb.append(line)
                line = reader.readLine()
            }
            reader.close()
            return sb.toString()
        }
    }

手順1. gradleの追記

まず、既存のプロジェクトがRetrofit2、Gson、Okhttpのライブラリが使えるようにします

build.gradle
dependencies {
      :
      :
    implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
    implementation "com.squareup.retrofit2:retrofit:"
}

手順2. JSONコードから値を取得するコードをリファクタリング

livedoor天気情報のAPIは以下のJSONコードを返します。

{
   "publicTime" : "2013-01-29T11:00:00+0900",
   "title" : "福岡県 久留米 の天気",
   "description" : {
      "text" : " 九州北部地方は、高気圧に覆われて晴れています。\n\n 29日は、九州北部地方では、高気圧に覆われて晴れますが、気圧の谷の\n影響で、昼過ぎから次第に曇りとなるでしょう。\n\n 30日は、気圧の谷の影響ではじめ曇りますが、昼頃からは高気圧に覆わ\nれて概ね晴れるでしょう。\n\n 波の高さは、九州北部地方の沿岸の海域では、29日は1.5メートル、\n30日は1メートルでしょう。豊後水道では、29日と30日は1メートル\nでしょう。\n 福岡県の内海では、29日と30日は0.5メートルでしょう。",
      "publicTime" : "2013-01-29T10:37:00+0900"
   },
   "link" : "http://weather.livedoor.com/area/forecast/400040",
   "forecasts" : [
      {
         "dateLabel" : "今日",
         "telop" : "晴のち曇",
         "date" : "2013-01-29",
         "temperature" : {
            "min" : null,
            "max" : {
               "celsius" : "11",
               "fahrenheit" : "51.8"
            }
         },
         "image" : {
            "width" : 50,
            "url" : "http://weather.livedoor.com/img/icon/5.gif",
            "title" : "晴のち曇",
            "height" : 31
         }
      },
      :
      :

リファクタリング前

上記のJSONコードからアプリの実装に必要な値は2つだけですが、
既存のコードはJSON構文の階層をたどって値を取得するため、コードが長く読みづらいです。

WeatherInfoReceiver.class
        override fun onPostExecute(result: String) {
            //JSON文字列からJSONObjectオブジェクトを生成。これをルートJSONオブジェクトとする。
            val rootJSON = JSONObject(result)
            //ルートJSON直下の「description」JSONオブジェクトを取得。
            val descriptionJSON = rootJSON.getJSONObject("description")
            //「description」プロパティ直下の「text」文字列(天気概況文)を取得。
            val desc = descriptionJSON.getString("text")
            //ルートJSON直下の「forecasts」JSON配列を取得。
            val forecasts = rootJSON.getJSONArray("forecasts")
            //「forecasts」JSON配列のひとつ目(インデックス0)のJSONオブジェクトを取得。
            val forecastNow = forecasts.getJSONObject(0)
            //「forecasts」ひとつ目のJSONオブジェクトから「telop」文字列(天気)を取得。
            val telop = forecastNow.getString("telop")

            
            
        }

        private fun is2String(stream: InputStream): String {
            val sb = StringBuilder()
            val reader = BufferedReader(InputStreamReader(stream, "UTF-8"))
            var line = reader.readLine()
            while(line != null) {
                sb.append(line)
                line = reader.readLine()
            }
            reader.close()
            return sb.toString()
        }

リファクタリング後

Gsonライブラリがあれば、JSONデータをJavaオブジェクトにデシリアライズしてくれます。
というわけで、復元したコードを格納するためのクラスを作っておきます。

    data class Repos(val description: Description, val forecasts : List<Forecast>)

    data class Description(val text : String)

    data class Forecast(val telop : String)

StreamをJSONコードに変換するis2Stringメソッドも不要です。
これだけで、随分とコードが短くできそうです。

手順3. HTTP通信のリファクタリング

今度は通信処理部分をリファクタリングしましょう。

リファクタリング前

HttpURLConnectionオブジェクトを生成して接続したらdisconnect()を呼んで切断する必要があります。
うっかり切断を忘れそうです。

WeatherInfoReceiver.class
        override fun doInBackground(vararg params: String): String {
            //可変長引数の1個目(インデックス0)を取得。これが都市ID
            val id = params[0]
            //都市IDを使って接続URL文字列を作成。
            val urlStr = "http://weather.livedoor.com/forecast/webservice/json/v1?city=${id}"

            //URLオブジェクトを生成。
            val url = URL(urlStr)
            //URLオブジェクトからHttpURLConnectionオブジェクトを取得。
            val con = url.openConnection() as HttpURLConnection
            //http接続メソッドを設定。
            con.requestMethod = "GET"

            :
            con.disconnect()
            :
            :
        }

リファクタリング後

接続/切断の儀式から解放されました。
HTTPのメソッドは@Get@Query で編集できるので拡張性も良さそうな印象。

    interface GitHubService {
        @GET("v1")
        fun fetchReposList(@Query("city") id: String): Call<Repos>
    }

    object APIClient {
        private const val BASE_URL = "http://weather.livedoor.com/forecast/webservice/json/"

        private fun restClient() : Retrofit {
            return Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
        }

        fun fetchReposList(id : String) : Response<Repos> {
            val service = restClient().create(GitHubService::class.java)
            return service.fetchReposList(id).execute()
        }
    }

完成

上記の作ったものを繋ぎ合わせて完成。
リファクタリング前と比べると、だいぶ短くなって可読性がよくなった感じがします。

MainActivity.kt

    data class Repos(val description: Description, val forecasts : List<Forecast>)

    data class Description(val text : String)

    data class Forecast(val telop : String)

    interface GitHubService {
        @GET("v1")
        fun fetchReposList(@Query("city") id: String): Call<Repos>
    }

    object APIClient {
        private const val BASE_URL = "http://weather.livedoor.com/forecast/webservice/json/"

        private fun restClient() : Retrofit {
            return Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
        }

        fun fetchReposList(id : String) : Response<Repos> {
            val service = restClient().create(GitHubService::class.java)
            return service.fetchReposList(id).execute()
        }

    }

    private inner class WeatherInfoReceiver(): AsyncTask<String, Repos, Repos>() {
        override fun doInBackground(vararg params: String): Repos {

            //可変長引数の1個目(インデックス0)を取得。これが都市ID
            val id = params[0]

            val response = APIClient.fetchReposList(id)
            return response.body()!!
        }

        override fun onPostExecute(result: Repos) {
            //天気情報用文字列をTextViewにセット。
            val tvWeatherTelop = findViewById<TextView>(R.id.tvWeatherTelop)
            val tvWeatherDesc = findViewById<TextView>(R.id.tvWeatherDesc)
            tvWeatherTelop.text = result.forecasts[0].telop
            tvWeatherDesc.text = result.description.text
        }
    }

RxJavaを使えば非同期処理部分にて、もっと短くできそうだと目論んでいます。
上手く行きそうでしたら、また記事が書きたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidのBottomNavigationViewの上にインジケータを楽に取り付け

やぁおはこんばんにちわ。
元気ですか?
いつもスッキリさせるものを紹介するんですが、今日もその類です。

6行程度の実装とレイアウトに少し変更を加えるだけで簡単に実装できるのでメモがてら共有ってかんじですね。
フルソースコードはこちら(Github) におきました。

まず動作を録画してGitアニメーションつくったのでみてください。

ezgif.com-video-to-gif (2).gif

どうやったかというとLinearLayout(Vertical)内のViewのWeightをコードからいじることでアニメーションさせるトリックをつかいました。
Kotlinのコードはこちら

    fun changeBottomNavProgress(index: Int) {
        if (index < 0) return
        val progress = (index) * 33f
        with(bottomNavProgressLeftPaddingView) {
            val lp = layoutParams as LinearLayout.LayoutParams
            lp.weight = progress
            layoutParams = lp
            (this.parent as ViewGroup).layoutTransition.enableTransitionType(android.animation.LayoutTransition.CHANGING)
        }
    }

差し込んだレイアウト側はこんな感じ

    <LinearLayout
        android:id="@+id/progressbar"
        android:layout_width="match_parent"
        android:layout_height="4dp"
        android:animateLayoutChanges="true"
        android:orientation="horizontal"
        android:weightSum="99"
        android:background="@android:color/darker_gray"
        android:layout_marginBottom="?attr/actionBarSize"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <View
            android:id="@+id/weight_progress_left_padding"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="0" />

        <View
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="33"
            android:background="@color/design_default_color_primary" />

    </LinearLayout>

動作はweight_progress_left_paddingのweightを0にしたり33にしたり66にしたりすると、隣にある青い色のViewが動いているように見えるようにつくってます。知ってみれば簡単なことですね。簡単だしシンプルだし。

android:weightSum="99"としてやるのが結構ミソです。まぁ3にしてval progress = (index)にしてもいいんですが・・・そこはご愛嬌です。レビューでは3にしろって言われるでしょうねw

アニメーションさせるにはKotlinコード側で(this.parent as ViewGroup).layoutTransition.enableTransitionType(android.animation.LayoutTransition.CHANGING)をsetLayout前後で書いてやる必要があります。
あとレイアウトXML側にもandroid:animateLayoutChanges="true"をつけてやらないと実行時に例外でコケます。

いやぁシンプルでいいですね。これでスッキリかけます。楽したい人はどうぞ。

(ちょっとレイアウトに差し込むのが面倒だったので、みんなも頑張って。リンクにおいたソースコードのやつ使えばいけそうだけどね!)

おまけ

プログレスバーの擬似実装もやってみました。コードは増えますがとてもシンプルなコードです!
ezgif.com-video-to-gif (1).gif

コードは上で紹介したリンクみてやってください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む