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

CLIでローカルビルドをUnityCloudBuildにアップ

※この記事を書いてる途中(2019/4/17)に ふとググったら去年10月公式に… :innocent:

それはさておき、2017年12月15日に

Unity Cloud Buildがローカル環境で作ったビルド成果物をアップロードできるようになった

という記事を見かけました。

しかし、WebからじゃなくCLIからアップロードしたいと思い、
きっと誰かが良い記事書いてくれるのでは…と思ってましたが
びっくりするくらい簡単なDeployGate1があるせいか、
はたまた そもそも興味持たれないのか 待てど暮らせど見つからず2
CloudBuildのページあたりを見ていましたらAPIドキュメント発見

CloudBuild API Document (v1.0.0時点)
https://build-api.cloud.unity3d.com/

 よし作ってみるか!

 …なるほど、まったくわからん :innocent:

とまでは行きませんでしたが
ChromeDeveloperで挙動やログを追いかけて なんとか出来ました 3
どれだけの方が利用するかわかりませんが、とりあえず公開

前提

  • 私の環境: MacOSX10.14.4(iMac4K)
  • 成果物は apk / ipa のみしかアップロードチェックしていません
  • unity DashBoard にて アプリ名登録まで済んでいるものとします
  • bashシェルスクリプトで順番に説明
  • json解析にjqを使用
  • 置換にgsedを使用
  • curl等、色々間違ってたらすいません…
  • ですので最低限のみ記載

以下スクショの文字列は例ですので DashBoard眺めてメモってください。
スクリーンショット 0031-04-17 18.06.45.png PROJECT_ID(ここではTest)と UPID(ここでは3dc…)をメモ

スクリーンショット 0031-04-17 18.06.20.png Testの下の ORG_ID(ここではdeveloper…)をメモ

成果物ファイル名は アスキーコード 0x20〜0x40 辺りを使ってましたら
%20〜%40 に変換してください(URLEncode?)

初期設定

API_KEY= #DashBoard - プロジェクト - Develop - Settings - Cloud Build - APIキーに書いてある
PROJECT_ID= #DashBoard - プロジェクト名
ORG_ID= #DashBoard - プロジェクト名の下に書いてある
UP_ID= #DashBoard - プロジェクト - プロジェクト名の横に書いてあるUPID
FILE_NAME= #成果物ファイル名 xxxx.ipa 等をURLEncode
EXT= #成果物の拡張子(つまりapkかipa)

FILE_SIZE= #成果物のファイルサイズ `wc -c < 成果物ファイル名` とか

if [ "${EXT}" = "apk" ]; then
    PLATFORM=android
    TYPE_NAME=".APK file"
    TYPE_NAMEe=".APK%20file"
elif [ "${EXT}" = "ipa" ]; then
    PLATFORM=ios
    TYPE_NAME=".IPA file"
    TYPE_NAMEe=".IPA%20file"
else
    exit 1
fi

BUILD_API_DOMAIN="https://build-api.cloud.unity3d.com/api/v1"
ARTIFACT_API_DOMAIN="https://build-artifact-api.cloud.unity3d.com/api/v1"
BUILD_TARGET_ID="_local"
LOG=`mktemp --tmpdir=./`

function endJob {
    echo $1
    rm ${LOG}
}

ビルドエリア生成して ビルド番号取得

curl -X POST -H "Content-Type: application/json" -H "Authorization: Basic ${API_KEY}" --data-binary "{\"platform\": \"${PLATFORM}\", \"label\": \"${COMMENT}\"}" ${BUILD_API_DOMAIN}/orgs/${ORG_ID}/projects/${PROJECT_ID}/buildtargets/${BUILD_TARGET_ID}/builds -o ${LOG} -s
BUILD_NO=`cat ${LOG} | jq .[0].build`
STAT=`cat ${LOG} | jq .[0].buildStatus`
if [ "${STAT}" != "\"success\"" ]; then
    endJob "BuildArea - Error: ${STAT}"
    exit 1
fi

アーティファクト生成

curl -X POST -H "Content-Type: application/json" -H "Authorization: Basic ${API_KEY}" --data-binary "{\"name\":\"${TYPE_NAME}\",\"primary\":true,\"public\":false,\"files\":[{\"filename\":\"${FILE_NAME}\",\"size\":${FILE_SIZE}}]}" ${ARTIFACT_API_DOMAIN}/projects/${UP_ID}/buildtargets/${BUILD_TARGET_ID}/builds/${BUILD_NO}/artifacts -o ${LOG} -s
STAT=`cat ${LOG}`
if [ "${STAT}" != "Created" ]; then
    endJob "Generate Artifact - Error: ${STAT}"
    exit 1
fi

アップロード

hc=$( curl -d "" -X POST -H "Upload-Length: ${FILE_SIZE}" -H 'Tus-Resumable: 1.0.0' -H 'Content-Type: application/offset+octet-stream' -H "Authorization: Basic ${API_KEY}" ${ARTIFACT_API_DOMAIN}/projects/${UP_ID}/buildtargets/${BUILD_TARGET_ID}/builds/${BUILD_NO}/artifacts/${TYPE_NAMEe}/upload/${FILE_NAME} -o ${LOG} -w '%{http_code}\n' -s )
if [ $hc != 201 ]; then
    STAT=`cat ${LOG}`
    endJob "Upload Post - Error: $hc\n${STAT}"
    exit 1
fi

hc=$( curl -X PATCH -H 'Upload-Offset: 0' -H "Content-Length: ${FILE_SIZE}" -H 'Tus-Resumable: 1.0.0' -H 'Content-Type: application/offset+octet-stream' -H "Authorization: Basic ${API_KEY}" --data-binary @"${1}" ${ARTIFACT_API_DOMAIN}/projects/${UP_ID}/buildtargets/${BUILD_TARGET_ID}/builds/${BUILD_NO}/artifacts/${TYPE_NAMEe}/upload/${FILE_NAME} -o ${LOG} -w '%{http_code}\n' -s)
if [ $hc != 204 ]; then
    STAT=`cat ${LOG}`
    endJob "Upload Patch - Error: $hc\n${STAT}"
    exit 1
fi

シェアリンク作成

# GET:既に生成済みのShareID取得、POST:ShareID生成、DELETE:ShareID削除
curl -d "" -X POST -H "Content-Type: application/json" -H "Authorization: Basic ${API_KEY}" ${BUILD_API_DOMAIN}/orgs/${ORG_ID}/projects/${PROJECT_ID}/buildtargets/${BUILD_TARGET_ID}/builds/${BUILD_NO}/share -o ${LOG} -s
STAT=`cat ${LOG} | jq .shareid | gsed 's/"//g'`
if [ "${STAT}" == "null" ]; then
    cat ${LOG}
    endJob "ShareLink - failed"
    exit 1
fi

SHARE_URL="https://developer.cloud.unity3d.com/share/${STAT}"

#ここらへんで SHARE_URL をチャット等に送る

endJob "success"

出来上がったSHARE_URLをチャットやメール等に流せばOKかと。
Android・iOSからダウンロード・インストール・実行出来ました。

みんな幸せにな〜れ :beers:

規約違反とかありましたら記事消します…。


  1. iOSのみならTestFlight、apkのみならGooglePlayテスト配信とかもありますね 

  2. (1週間待っただけですが) 

  3. 実際作ったのは2017年12月28日。1年以上寝かしてました。 

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

[Android] ConstraintLayoutでwrap_contentしつつ領域からはみ出さないようにする

実現したいレイアウト

スクリーンショット 2019-04-16 20.54.27.png

特に何の変哲もないレイアウトだが、これをConstraintLayoutで作るのに手こずった話。

素直なConstraintLayout実装

とりあえず上記のレイアウトを素直に作ると以下のようになる。

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="70dp"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="#FFDDDD">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:text="販売価格"
        android:textSize="25dp"
        />

    <TextView
        android:id="@+id/price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@id/title"
        android:layout_marginLeft="10dp"
        android:lines="1"
        android:autoSizeTextType="uniform"
        android:text="2,800"
        android:textSize="40dp"
        />

    <TextView
        android:id="@+id/yen"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@+id/price"
        app:layout_constraintBaseline_toBaselineOf="@+id/price"
        android:gravity="bottom"
        android:text="円"
        android:textSize="20dp"
        />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:text="購入する"
        android:textSize="25dp"
        />

</androidx.constraintlayout.widget.ConstraintLayout>

問題点

しかし、このレイアウトには問題があり、価格が大きくなるとボタンと価格が重なってしまう。
※分かりやすくするためボタンは半透明化
スクリーンショット 2019-04-16 21.01.00.png

解決方法

  1. 値段layout_constrainedWidthtrue にする
  2. の右端を購入するの左端につける
  3. 値段の右端をの左端に付ける
    <TextView
        android:id="@+id/price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@id/title"
        app:layout_constraintRight_toLeftOf="@+id/yen"
        android:layout_marginLeft="10dp"
        android:lines="1"
        android:autoSizeTextType="uniform"
        android:text="2,811"
        app:layout_constrainedWidth="true"
        android:textSize="40dp"
        />

    <TextView
        android:id="@+id/yen"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@+id/price"
        app:layout_constraintRight_toLeftOf="@+id/button"
        app:layout_constraintBaseline_toBaselineOf="@+id/price"
        android:gravity="bottom"
        android:text="円"
        android:textSize="20dp"
        />

layout_constrainedWidthtrue にすることで wrap_contentの中身が大きくても、Constraintをはみ出さないよう幅を制限できる。
また、layout_constrainedWidthを有効にするために、左右両端にConstraintをつける必要がある。

スクリーンショット 2019-04-17 17.51.06.png

これで価格がはみ出ることはなくなった。

位置調整

しかし、上の方法ではまだ問題があり、価格が短い場合は価格と円が左寄せにならなくなってしまう。

スクリーンショット 2019-04-17 17.52.18.png

これを調整するために以下の対応が必要になる。

  1. layout_constraintHorizontal_chainStyle="packed" を指定して価格と円をくっつける
  2. layout_constraintHorizontal_bias="0" を指定して左端によせる

layout_constraintHorizontal_chainStyle は双方向にConstraintで結ばれたView同士をくっつけるか、分散させるかを指定することができる。
これをpackedにすることで価格と円がくっつくが、標準では中央寄せになってしまうので、位置を左寄せにするため layout_constraintHorizontal_bias を0に設定する。

以下が最終的なレイアウトファイル。

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="70dp"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="#FFDDDD">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:text="販売価格"
        android:textSize="25dp"
        />

    <TextView
        android:id="@+id/price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toRightOf="@id/title"
        app:layout_constraintRight_toLeftOf="@+id/yen"
        android:layout_marginLeft="10dp"
        android:lines="1"
        android:autoSizeTextType="uniform"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintHorizontal_bias="0"
        android:text="300"
        app:layout_constrainedWidth="true"
        android:textSize="40dp"
        />

    <TextView
        android:id="@+id/yen"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@+id/price"
        app:layout_constraintRight_toLeftOf="@+id/button"
        app:layout_constraintBaseline_toBaselineOf="@+id/price"
        android:gravity="bottom"
        android:text="円"
        android:textSize="20dp"
        />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:text="購入する"
        android:textSize="25dp"
        />

</androidx.constraintlayout.widget.ConstraintLayout>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android build時にapkにcommitHashを埋め込んでアプリ上で確認する

自分用メモ

/app/build.gradle

//commit hash 取得
def getCommitHasH() {
    def process = "git rev-parse --short HEAD".execute()
    process.waitFor()
    (process.exitValue() == 0) ? process.text.trim() : ""
}

android {
    defaultConfig {
        // BuildConfig へ埋め込み
        buildConfigField("String", "COMMIT_HASH", "\"" + getCommitHasH() + "\"")
    }
}

kotlinで表示制御する場合
HogeActivity.kt

//debugビルドなら表示する
if(BuildConfig.DEBUG){
  commitHashText.text = BuildConfig.COMMIT_HASH
  commitHashText.visibility = View.VISIBLE
}else{
  commitHashText.visibility = View.GONE
}

layoutxmlで表示制御する場合

<!-- import BuildConfig -->
<data>
    <import type="com.hoge.BuildConfig"/>
</data>

<!-- Data binding -->
<TextView
    tools:text="@{BuildConfig.COMMIT_HASH}"
    android:visibility="@{BuildConfig.DEBUG ? View.VISIBLE : View.GONE}"
/>

ref
- https://gist.github.com/kappa-lab/fd8650dfcc2f780055255fa982e82473
- https://qiita.com/kawachi/items/d97c448b013c37f2f198

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

Kotlin Serialization事始め

はじめに

僕が神と崇めているJake Wharton氏(@JakeWharton)のGitHubを眺めていたら、 Kotlin Serialization なるものを使っていることが分かりました。神が使っているものだから、きっと我々下々の民に素晴らしい恩恵を与えるものに違いない:relaxed:そう思い使い方を調べてみました。

Kotlin Serializationとは

Kotlin serialization consists of a compiler plugin, which automatically produces visitor code for classes, and runtime library, which uses generated code to serialize objects without reflection.

公式リポジトリの説明によると、『Kotlin Serializationはクラスのビジターコードを自動的に生成するコンパイラプラグインと、生成されたコードを使用してリフレクションなしでオブジェクトをシリアル化するランタイムライブラリで構成されている』そうです。はぁ:confused:

さらに、Kotlin Serializationは以下の機能を備えているそうです。

  • @Serializable及び標準コレクションとしてマークされているKotlinクラスをサポート
  • JSON、CBOR及びProtobuf形式をサポート
  • 同様のコードがJVM/JS/Nativeで機能するマルチプラットフォーム

要するに、Web APIなどのレスポンスをサービスで利用できる形式のオブジェクトに変換する、パーサのようなものかなと。JSONのパーサだと、GsonやMoshiなどが有名ですね。また、これらのパーサと比べて、リフレクションを使用していないため、動作が早いんだとか。ほほう。

導入

コンパイラプラグインをプロジェクトルートに追加します。

build.gradle
buildscript {
    ext.kotlin_version = '1.3.30'

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
    }
}

なお、Kotlinのバージョンは1.3.30以上が必要とのことです。続いて、モジュールに依存関係とプラグインに関する追記をします。

build.gradle
apply plugin: 'kotlinx-serialization'

repositories {
    maven { url "https://kotlin.bintray.com/kotlinx" }
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0'
}

全ての追記が終わったらSync Projectを実行し、エラーが出ていなければOKです。

使い方

以下のようなJSON形式の場合

repo.json
{
    "name": "dagger",
    "description": "A fast dependency injector for Android and Java. http://google.github.io/dagger",
    "language": "Java",
    "html_url": "https://github.com/google/dagger",
    "watchers_count": 563,
    "stargazers_count": 13294,
    "forks_count": 2672,
    "owner": {
        "login": "google",
        "avatar_url": "https://avatars1.githubusercontent.com/u/1342004?v=4"
    }
}

こんなモデルを作ります。

Repo.kt
@Serializable
data class Repo(
    val name: String,
    val description: String? = null,
    val language: String? = null,
    @SerialName("html_url") val url: String,
    @SerialName("watchers_count") val watchers: Int,
    @SerialName("stargazers_count") val stars: Int,
    @SerialName("forks_count") val forks: Int,
    val owner: Owner
)
Owner.kt
@Serializable
data class Owner(
    val login: String,
    @SerialName("avatar_url") val avatar: String
)

@Serializableアノテーションを付けたKotlinクラスがシリアライズの対象になります。@SerialNameアノテーションはJSONのキー名と違ったパラメータを紐づける場合に使います。その他のアノテーションは公式のドキュメントを参考にしてください。

Gson/Moshiからの移行

Retrofit+GsonあるいはRetrofit+Moshiなどで、すでにAPIを叩く処理を実装している場合、Kotlin Serializationへの移行はできないのではないか?安心してください。神は我々を見捨てませんでした:pray:

Kotlin Serialization Converter

Retorfit向けにコンバータを用意してくれていたのです。このコンバータをGsonやMoshi同様に

Api.kt
val contentType = MediaType.get("application/json")
val retrofit = Retrofit.Builder()
    .baseUrl("https://example.com/")
    .addConverterFactory(Json.asConverterFactory(contentType))
    .build()

とすれば、Kotlin Serializationが使えます。今後正式にRetrofitのプロジェクトに採用されるかもしれません。

まとめ

JSON以外にもパースできる形式があったり、独自のシリアライズを定義することもできるのですが、今回はひとまず概要のみで割愛させていただきました。導入に関しては、使用可能なKotlinバージョンの縛りはあるものの、GsonやMoshi同様にアノテーションで簡潔に書くことができました。導入コストも比較的低いのではないでしょうか。また、先述した動作速度の向上に関しても気になるところではあるので、時間がある時に他のパーサと比較してみたいと思います。今回はこのへんで。

参考文献

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

Xamarin 最近どうよ?

「ネイティブアプリ開発者は絶滅危惧種なのか?」への感想文 - ナカザンドットネット

Office 365, MS teams, Skype, @code, and the edge debug protocol are being rewritten in js instead of C++ with special MS tooling : programming

への反応で、「最近 Xamarin 生きてんの?」という声がちらほら聞こえたので書いてみました。

もともとの 「ネイティブアプリ開発者は絶滅危惧種なのか?」 という問いには、「緩やかにそうなっていくでしょうね」 という冒頭の記事を書かれた @Nkzn さんとほぼ同じアンサー1 なので、この話題を Xamarin と絡めたらというネタで書きます。

マイクロソフトなのになんで React 使ってんの?Xamarin は?

Office も Teams も Skype も、全て Web アプリも含めての rewrite 構想だったのでしょうね。
Webを含めるという戦略も当然だし、Xamarin は Webアプリはカバーしない ので、至極当然の選択です、以上です。

近年のマイクロソフト

ここ数年のマイクロソフトは、軸足を完全に Azure、つまりサーバーサイドに移していて、クライアントサイドのプラットフォーム(Windows)で覇権を取ることはあきらめたように見えます。

フロントエンド技術への注力で目立つものは、

  • TypeScript 2
  • Xamarin
  • HoloLens

くらいで、いわゆる「Windows向けアプリケーション」を開発するための、

  • WPF(Windows Platform Foundation)
  • UWP(Universal Windows Platform)

といった領域の新機能はあまり目立ちません。

クロスプラットフォームな .NET 開発/実行環境である .NET Core

一方でマイクロソフトは、サーバーサイドというか、 Windows 以外のプラットフォームで .NET 資産を使えるようにする ことに注力し続けていて、それが .NET Core です。

.NET Core は 本当にクロスプラットフォーム で、 Linux や mac で同じバイナリが動作します。
AWS の Lambda も .NET Core をサポートしているし、Dockerコンテナ も普通に用意されています。

「は?オレ今まで .NET 使ってないから関係ねーし」と言われればそれまでですが、この先ずっと同じ言語を使って働いていくとも限らないですし、選択肢が増えるのは良いことです。

マイクロソフト自身が「脱Windows」している

個人的にもっと象徴的だなあと思ったのが SQL Server on Linux です。

MS の RDBサーバーである SQL Server は当然 Windows OS 専用でしたが、それすら Linux 版を作ってしまいました。

このように、マイクロソフト自身が Windows を捨てる準備を着々としているように感じられます。

Xamarin は最近どうなのよ?

さて、ネイティブアプリ開発が PWA に喰われ、クロスプラットフォームなネイティブアプリ開発は Flutter に押されと、オワコン説もささやかれる Xamarin ですが、Xamarin の最近の動向を書いてみます。

そもそもブランドとしての Xamarin とは、

  • Android や iOS の API を .NET 向けにラップした Xamarin Native(Xamarin.Android や Xamarin.iOS)
  • ↑を利用した、Android/iOS/etc共通のUI/アプリケーションフレームワークである Xamarin.Forms 3

に大別されます。

Xamarin Native は縁の下の力持ちというか、Android,iOSそれぞれのSDKのバージョンアップに追従して粛々とバージョンアップをし続けていて、それだけでとても価値があります。逆に言えば、ここでは皆が驚くような新機能は今後も出てこないでしょう。

Xamarin.Forms が、Flutter や ReactNative と直接の競合関係になる箇所です。
Flutter の「Materialで美麗なアプリを爆速で」とか、ReactNativeの「WebもAndroidもiOSも」に比べると弱いですが、機能追加はしています。最近だと、

  • FlexLayout - FlexBox みたいなやつ、というかそれ
  • Xamarin.Forms Visual - Flutter よろしく Android/iOS で Material な見た目を実現できるやつ

Xamarin.Forms Visual は実現方法は違えど、対Flutterとしては効果が期待できそうな雰囲気を感じます(目的はそれかは知らない)。

image.png

Visual Challenge Conquered! | Xamarin Blog より

今後私は Xamarin と、あるいはネイティブアプリとどう向き合って行くのか?(持論)

Xamarin に関しては、徐々にシェアは減っていくのでしょうが、ぽんぽこ風に言えば「それでもどっこい生きている」でしょうか。意外としぶといんですよ、.NET が死ななければ Xamarin も死なないし。

私自身は、「PWAファースト」な考えをしています。
最近は Glide という Spreadsheet から PWA が自動生成できる サービスに注目していて、そう言えばこれは Xamarin からスピンアウトした人達が作っているサービスでした。

スピンアウトした彼らは、ネイティブアプリからPWA移行の急先鋒と言えるのかも知れないですが、この Glide に注目している理由を、冒頭の @Nkzn さんのブログから引用します。

BtoBでお客さんが「アプリが欲しい」と言った場合に、実はそれは素朴なWebアプリでいい要件だったり、少し込み入ってもPWAの範囲でなんとかなるものは、意外と多いのではないでしょうか(肌感覚です)。

こういう肌感覚を私も持っていて、Glide ってまさにこういうニーズにぶっ刺さるんじゃないかな、と感じるからです。
Salesforce みたいなツールを導入するコストは無い、業務は EXCEL で回っているみたいな中小企業さん向けのソリューションですね。
Glide がそのまま使えるケースは限られるでしょうが、開発の参考にする価値は大いにあるなあと。

PWA の次の選択肢として検討するのが「ガワネイティブ」ですね。
DroidKaigi 2019 の私の発表 で、「再考!ガワネイティブ」という章を設けました。

image.png

これの意図するところは、

  • プレゼンテーション層は PWA(というか Web)
  • デバイス連携はネイティブ

とすることで、現状 Web では苦手な機能を部分的にネイティブにやらせることで、いいとこ取りが簡単にできるじゃん?というものです。

で、このデバイス連携箇所をネイティブで作る際、Xamarin(Xamarin Native)が活躍します。
例えば 「カメラで画像を撮影して Android の TensorFlow Lite や iOS の CoreML で解析してその結果を返す」 という一連の処理をそれぞれ実装して、抽象化されたAPIを定義し、それを JavaScript と連携させて PWA から呼び出す、ということは、現状もっとも作りやすいのは Xamarin と .NET です。
Flutter や ReactNative や Cordova などはどうしてもネイティブとのブリッジが面倒です。その面倒さは API をラップしている Xamarin Native にはほぼありません(何度言っているのだろうこのセリフ)。
ただ、Kotlin Multiplatform がもっと進化したら置き換えられる可能性はあります、期待しています。

おわりに

まとめると、

  • モバイルネイティブアプリ開発者は徐々に絶滅危惧種になっていくと思う
  • Xamarin はそれでもどっこい生きている(く)
  • 10年後のことは誰にもわからない

モバイル "ネイティブ" アプリ開発案件の数は減っていくと思っていて、その中のクロスプラットフォームアプリ開発ツールと領域でも Flutter や RN、Kotlin MPP とパイを取り合う構図なので、Xamarin へのニーズも徐々に減っていくと思います(クロスプラットフォームはモバイルに限った話ではない、という話ならそれは Xamarin ではなく ".NET" ですし)。

一方で .NET の利用シーンは増えている(Azure, ゲーム(Unity), HoloLens など)ので、「周りがほとんど .NET だった時にモバイルアプリが必要になったら Xamarin」という選択にはなると思います。JavaScript はほぼどこでも使えるようになっているし、Kotlin もそうなりつつあります。Dart はまだ未知数という感じでしょうか。

Android Dev Phone 1 を $400 で買って、自分で書いたアプリケーションがその端末で動いたとき、めちゃくちゃ感動したし、なんだかものすごい未来が来る予感がして、実際(概ね)そうなったわけですが、その10年前と同じ感動と予感が得られる「何か」をずっと探していて、そのために、スマホアプリに限らずいろいろなテクノロジーに手を出しています。たぶんこれからもそうして生きていくと思います、それが楽しいです。


  1. ポジションも React を Xamarin に置き換えればほぼ同じだわw 

  2. 言語という意味では C# もガンガン進化してます 

  3. もはやこちらが「Xamarin」として認知されているのが現状ですが、そうでは無いことをご理解いただきたく。 

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

Glideで個人間で使う旅行計画PWAアプリをつくってみた

はじめに

ネイティブアプリデベロッパーからしたら驚愕のWebサービスが出てきました。
当方もネイティブアプリ開発をしていますが、15分足らずでPWAアプリが作れるのは脅威的だと思います。

本記事では、ざっくり作り方、Glideでできることできないことを明確にしていきたいと思います。
(記事執筆時 2019/04/17 時点)

作成したアプリになります。ご査収ください。

スポット一覧

スクリーンショット 2019-04-16 23.53.19.png

スケジュール一覧

スクリーンショット 2019-04-17 0.21.56.png

GWにタイ旅行に行くので、知り合いとスポットを管理できたら便利だなぁと思い作成しました。

アプリ前提

  • データ作成は、本アプリからユーザが行う(PC使えない場合も踏まえて)
  • 少人数での利用(2-3人)

GlideでのPWAアプリ作り方

ぶっちゃけある程度はポチポチしてるだけで作れるので説明はあまりいらないかと思いますが、一応つけときます。

  1. Glide に登録し「New App」 スクリーンショット 2019-04-16 23.55.30.png
  2. Google Spreadsheetでデータの作成 スクリーンショット 2019-04-16 23.57.21.png
  3. Glideでデータを選択 スクリーンショット 2019-04-16 23.58.34.png
  4. Glide画面のNavigation, Screen, Settings で入力項目や表示項目を設定していく スクリーンショット 2019-04-17 0.01.48.png

めちゃくちゃ簡単っ!!

Glideでの操作

  • Navigation : Spreadsheetのシートに応じてメニューを増やすことができます スクリーンショット 2019-04-17 0.06.17.png
  • Screen : 画面の表示項目や入力項目を追加できます
    スクリーンショット 2019-04-17 0.06.29.png

  • Settings: アプリ名、ロゴを決めたり、PWAリンクを設定できます

ドメインを登録するには課金しないとみたいです
http://help.glideapps.com/faq/setting-a-custom-domain-for-your-app

スクリーンショット 2019-04-17 0.05.32.png

Glideでできなかったこと、注意すること

正直ここまで簡単にアプリが作れるので、文句はないのですが、これができればなぁという一覧です。まあアップデートでなんとでもなると思うので今後も期待ですね。

  • 写真はURLの直アドレスのみ

  • アプリ上から画像の保存は不可

    • 手入力で画像アドレスをはっつけるのはめんどい!と思い、ないかなと期待しましたが、残念ながらできない様子
  • データ入力項目はテキストのみのため、日付項目などは選択できない

    • (今のところ)データの入力項目はNote、テキスト、Switchのみです。
  • Spreadsheetの計算式は利用できない?

  • Navigation(アプリメニュー項目)を増やすと表示項目などの設定が初期化される(バグ?)

    • 途中まで作成してあとからメニューを増やそうとしたら表示項目などが初期化されてしまい、また最初からになりました。(簡単だからいいけど、さすがにめんどくさい。。。)
  • Setting(入力項目や表示項目)のエクスポートができない

    • 入力項目や表示項目のエクスポートはできず、同様にアプリをコピーとかもできないようです。

さいごに

無料かつ短時間でノンプラグラミングでここまでのアプリができてしまうGlide。個人、企業で利用しない手はないですね!
個人的にはもっと細かく設定できたり、設定をエクスポートできたらなぁという思いがありますが、今後有料化したりして機能が増えていけばなぁと思う次第です。

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