20210314のAndroidに関する記事は8件です。

SeekBarをカスタマイズする

UIパーツは基本的にシステムデフォルトのものをそのまま使いたい主義者なのですが、こういうデザインにしたいと要望を受ければ頑張らねばということで、SeekBarのカスタマイズってどうやるんだけっけ、をまとめてみます。

Androidのデフォルトのシークバーは、(プロジェクトのテンプレートから作成した場合のTheme.MaterialComponents系テーマを使ったものの場合、以下このテーマを前提にしています)
以下のような見た目をしています。

テーマカラーの変更

単に色を変更するなら、ならcolorSecondaryで指定した色が使われるためこれで変更できます。

<style name="Theme.MyApplication" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
    <item name="colorSecondary">@color/purple_500</item>

バーが伸びる前のところの色は、Theme.MaterialComponentsなら、colorControlNormalの色と、android:disabledAlphaのα値を掛け合わせたものになっており、これで変更可能です。

<style name="Theme.MyApplication" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
    <item name="colorControlNormal">@color/purple_500</item>
    <item name="android:disabledAlpha">1.0</item>

つまみの形状変更

移動させるところのつまみ部分はレイアウトXMLにてandroid:thumbでDrawableを指定することで変更可能です。

<SeekBar
    android:thumb="@mipmap/ic_launcher"

Screenshot_1615723227.png

バーの形状変更

バーの形状変更はちょっと面倒です。DrawableをレイアウトXMLにてandroid:progressDrawableでDrawableを指定することで変更可能です。

<SeekBar
    android:progressDrawable="@drawable/progress"

ここで指定するDrawableは特定のIDを持つlayer-listである必要があります。
ベースの部分が@android:id/background、プログレスの部分が@android:id/progressです。
secondaryProgressが必要な場合は@android:id/secondaryProgressを使いますが、これは今回扱いません。

まずは単純に色を指定します。progressについては、この色で上書きするため<clip>で囲んで指定します。

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <color android:color="#000" />
    </item>

    <item android:id="@android:id/progress">
        <clip>
            <color android:color="#f00" />
        </clip>
    </item>
</layer-list>

すると以下のようになります。
ちょっと思ってたのと違いますよねー

ちなみに、この大きさはドラッグ中のthumbの大きさと同じになっているみたいですが、thumbを他のDrawableにしたり、SeekBarにpaddingを設定しても変わらないようです

device-2021-03-14-211948.png

形状を角丸にしてみます。

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <shape android:shape="rectangle">
            <corners android:radius="16dp"/>
            <solid android:color="#000"/>
        </shape>
    </item>

    <item android:id="@android:id/progress">
        <clip>
            <shape android:shape="rectangle">
                <corners android:radius="16dp"/>
                <solid android:color="#f00"/>
            </shape>
        </clip>
    </item>
</layer-list>

角丸にはなりましたね。うーん、でも太い

XMLだけでなんとかする場合、layer-listのitemにはtop/bottomのマージンを設定できますね。

<?xml version="1.0" encoding="utf-8"?>
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@android:id/background"
        android:bottom="8dp"
        android:top="8dp"
        >
        <shape android:shape="rectangle">
            <corners android:radius="8dp" />
            <solid android:color="#000" />
        </shape>
    </item>

    <item
        android:id="@android:id/progress"
        android:bottom="8dp"
        android:top="8dp"
        >
        <clip>
            <shape android:shape="rectangle">
                <corners android:radius="8dp" />
                <solid android:color="#F00" />
            </shape>
        </clip>
    </item>
</layer-list>

ってことでわずかに角丸になったSeekBarを作ることができます。

ただし、paddingで合わせるのは本来の方法ではないと思います。高さが変わったときなどに都度paddingの調整が必要になっていまいます。ですので、9-patchを使うほうが良いと思います。

9-patchを使ったバーのカスタマイズ

9-patchならさらに複雑な形状にもできますね。左右にぽっちがあるプログレスバーみたいなのを作りたいとします。

この場合、以下の2つの9-patch画像を作ります。

これをlayer-listから指定します。

<?xml version="1.0" encoding="utf-8"?>
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <selector>
            <item android:drawable="@drawable/seek_background"/>
        </selector>
    </item>

    <item android:id="@android:id/progress">
        <clip>
            <selector>
                <item android:drawable="@drawable/seek_progress"/>
            </selector>
        </clip>
    </item>
</layer-list>

以上です。

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

Flutterで普通のスワイプで画面をpopする

FlutterでNavigator.push(context)して遷移した先の画面で元の画面に戻るときにはNavigator.pop(context)をアイコンに設置したりエッジスワイプしたりしますよね。
通常のスワイプでも戻したかったのでやり方を見つけるのに少々苦労しましたがなんとかできました。

GestureDetector(
  onHorizontalDragUpdate: (details) {
    if (details.delta.dx > 18) {
      Navigator.pop(context);
    }
  },
  child: ・・・,
);

このようにGestureDetector.onHorizontalDragUpdateを設定して横方向のスワイプをトリガーにnavigator.pop(context)を実行するようにしました。
details.delta.dxはスワイプの速度で、正の向きが左→右です。しきい値として設定した18を超えるとnavigator.pop(context)を実行します。この値は実機で触りながら調整しました。

参考

Flutter公式ドキュメント onHorizontalDragUpdate
https://api.flutter.dev/flutter/widgets/GestureDetector/onHorizontalDragUpdate.html

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

React/スマホアプリ開発未経験者がReact NativeでiOSアプリを作ってみた

JavaScript(フレームワーク未使用)業務経験1年・React/React Nativeもスマホアプリ開発のいろはも分からなかった私が、平日夜・休日の時間を使って、約2ヶ月(80時間)でReact NativeでiOSアプリを開発したときの流れや参考になった記事・動画を紹介します!

自分自身、何度も壁に打ち当たり、心が折れそうだったので、少しでも役に立てればと思います!
あと、少し込み入った問題になると、英語のサイトがかなり多くなってくるので、ポジティブに捉えるなら、良い英語の勉強になると思います!笑

作った単語帳アプリlankiageこちら
ソースコードはこちら

そもそもReact/React Nativeとは?

Reactは、Facebook社が開発した、WebアプリケーションのUIを構築するためのライブラリです。
JavaScript三大フレームワーク・ライブラリとして、他にVue.jsやAngular.jsがありますが、世界的にReactが最も利用されているため、フロントエンドとしては最も身につけておくべきスキルだと思います。

一方、React Nativeは、JavaSciptでiOSアプリ・Androidアプリを、同時に、かつReactライクに開発できるフレームワークで、いわゆるクロスプラットフォームフレームワークの1つです。
従来は、OSに合わせて言語を使い分ける必要がありましたが、これによってコード1つで両方のアプリが開発できるわけです。
現在は、Flutterというフレームワークの方が注目されているという記事もありますが、DartというGoogleの独自言語ということもあり、React NativeはJavaScirpt経験者がさらっとスマホアプリに手を出すにはもってこいのライブラリだと思います。

1. 開発環境構築編

1-1. 開発プロジェクトのインストール

coming soon...

1-2. Xcode

coming soon...

2. 実装編

実際に使用した主なライブラリについてご紹介します。

2-1. Realm(ローカルデータベース)

coming soon...

2-2. react-native-vector-icons(アイコン)

豊富なアイコンが使える便利なライブラリですが、かなりビルドに苦労しました。。。

特にこのステップを見落としがちなので、丁寧にやっていきましょう!

2-3. React Navigation(画面遷移)

画面間の遷移を扱うライブラリです。
似たライブラリとしてReact Native Navigationもありますが、設定ファイルであるAppDelegate.mをいじる必要があった上に、使い勝手が良くないと感じたので、今回はReact Navigationを使うことにしました。

3. アプリ申請編

coming soon...

4. 一通り開発をやってみて...

coming soon...

5. 実際に開発したアプリ

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

初めてReact NativeでiOSアプリを開発した時に参考にした記事・サイトたち

JavaScript(フレームワーク未使用)業務経験1年・React/React Nativeもスマホアプリ開発のいろはも分からなかった私が、平日夜・休日の時間を使って、約2ヶ月(80時間)かけてReact NativeでiOSアプリを開発したときの流れや参考になった記事・動画を紹介します!

自分自身、何度も壁に打ち当たり、心が折れそうだったので、少しでも役に立てればと思います!
あと日本語のドキュメントが少ないため、英語のサイトを見ることも多くなりますが、英語の良い勉強の機会だと思って、めげずに頑張りましょう!!

作った外国語用単語帳アプリ"lankiage"はこちら(App Store)
ソースコードはこちら(Git Hub)

そもそもReact/React Nativeとは?

Reactは、Facebook社が開発した、WebアプリケーションのUIを構築するためのライブラリです。
JavaScript三大フレームワーク・ライブラリとして、他にVue.jsやAngular.jsがありますが、世界的にReactが最も利用されているため、フロントエンドとしては最も身につけておくべきスキルだと思います。

一方、React Nativeは、JavaSciptでiOSアプリ・Androidアプリを、同時に、かつReactライクに開発できるフレームワークで、いわゆるクロスプラットフォームフレームワークの1つです。
従来は、OSに合わせて言語を使い分ける必要がありましたが、これによってコード1つで両方のアプリが開発できるわけです。
現在は、Flutterというフレームワークの方が注目されているという記事もありますが、DartというGoogleの独自言語ということもあり、React NativeはJavaScirpt経験者がさらっとスマホアプリに手を出すにはもってこいのライブラリだと思います。
※React Native勉強中にFlutterの勢いを知ったのはちょっとショックでした(汗)

1. 開発環境構築編

1-1. 開発プロジェクトのインストール

coming soon...

1-2. Xcode

coming soon...

2. 実装編

今回、実際に使用したライブラリについてご紹介します。

2-1. Realm(モバイルデータベース)

世はクラウドブームですが、今回は全てデータをモバイル(ローカル)で保存することにしました。(バックエンドも開発するとなると、かなりの負荷があるので、やめました。)
その際に使用したライブラリがこのRealmです。
他にCoreDataやSQLiteというのがあるようですが、realmが話題のようなので、これを使いました。
※React Native Expoを使っている方は、Realmが利用できないので、ご注意ください。

2-2. react-native-vector-icons(アイコン)

豊富なアイコンが使える便利なライブラリですが、かなりビルドに苦労しました。。。

特にXcodeの設定を変更するこのステップを見落としがちなので、丁寧にやっていきましょう!

2-3. React Navigation(画面遷移)

画面間の遷移を扱うライブラリです。
似たライブラリとしてReact Native Navigationもありますが、設定ファイルであるAppDelegate.mをいじる必要があった上に、使い勝手が良くないと感じたので、今回はReact Navigationを使うことにしました。

2-4. react-native-picker-select(セレクトボックス)

React Nativeのセレクトボックスライブラリは中々少ないようで、今回はこれを使いました。ちょっと微妙ですが。。

2-5. その他

その他、使用頻度は高くないかもしれませんが、今回使用したライブラリはこちらです。

3. アプリ申請編

基本的には、[2020年版]AppleにiOSアプリを申請する方法を参考に進めましたが、
まず第一にやるべきは、Apple Developer Programへの登録です。
(年間税込¥12,980と高額ですが、目を瞑りましょう。。)
進めていく中で一番厄介だったのが、スクリーンショット登録だったので、それについて解説します。

スクリーンショット登録

App Storeのプレビューに表示される、いわばそのアプリの顔ともなる重要な部分です!
下の「スクリーンショット」の3枚がこれに当たります。

画面のスクリーンショットを全面にしても申請は通りますが、そのアプリのメリットや特徴をできるだけ一目でわかるように工夫しましょう。

2021年3月現在、iPhone 6.5インチ、iPhone 5.5インチ、iPad 12.9インチの3サイズのスクリーンショットをそれぞれ3つ登録する必要があるので、厄介ですが丁寧にやりましょう!

では、どのように良い感じのスクリーンショットを作るかですが、、無料でやるのにあまり参考にできるサイトがなかったので、簡単に私が行ったやり方を共有します。

  1. 登録すべきスクリーンショットサイズのシミュレータ(3種類)を起動し、スクリーンショットを撮る。
  2. AppLauchPadでGLOBAL SCREENSHOTから1.で撮ったスクリーンショットを選択(5.5インチでやると、iPhoneのフレームに余白がありますが、無視して大丈夫です。)
  3. ダウンロード画面から、エクスポートしたいサイズのスクリーンを右クリックで「画像をコピー」
  4. keynoteでスライドサイズをスクリーンショットのサイズにカスタマイズし、3.でコピーした画像を貼り付けて、編集する。(keynoteのインスタントアルファ機能を使うと、背景の白地がなくなり、綺麗にフレームだけを切り出せます!)

4. 一通り開発をやってみて...

一番の感想は、ビルドのエラーが多すぎて辛いということ(泣)
こればかりは初心者のうちは仕方ないですかね、、
ただ徐々に耐性がついてきたので、ある意味エンジニアとしてスキルアップしたと思います。笑

あとは、進めていく中でReactのコーディングのトレンドを知っていき、自分がいかにレガシーな書き方をしていたかということに気づかされました、、
途中で引き返しにくかったので、諦めましたが、最低限以下くらいは知っておいた方が良いと思います。
・TypeScript > JavaScript
・関数コンポーネント > クラスコンポーネント
・Stateの管理はReduxでやるべし
記事を調べる場合は、できるだけ新しいものに触れることをオススメします。

【参考記事】

5. 実際に開発したアプリ

coming soon...

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

StateListAnimatorで色々なアニメーションを作る

最近StateListAnimatorをよく使っているので、備忘録がてらそのプラクティスを纏めてみようと思います。

StateListAnimatorとは

ViewのStateに合わせてプロパティアニメーションを定義できるものです。
ドキュメントは以下の通りです。
- StateListAnimator

例)指の押し/離しでアニメーションを変えてみる

簡単な例を実装してみます。
ボタンを押した時にViewが沈み込み、離した時に戻るようなアニメーションです。

click_animator.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"> <!-- 押した時に縮小 -->
        <set>
            <objectAnimator
                android:duration="150"
                android:propertyName="scaleX"
                android:valueTo="0.5f" />
            <objectAnimator
                android:duration="150"
                android:propertyName="scaleY"
                android:valueTo="0.5f" />
        </set>
    </item>
    <item android:state_pressed="false"> <!-- 離した時に戻る -->
        <set>
            <objectAnimator
                android:duration="150"
                android:propertyName="scaleX"
                android:valueTo="1.0f" />
            <objectAnimator
                android:duration="150"
                android:propertyName="scaleY"
                android:valueTo="1.0f" />
        </set>
    </item>
</selector>

main_fragment.xml
...
        <!- android:stateListAnimator に指定 -->
        <Button
            android:id="@+id/animation_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_blue_bright"
            android:stateListAnimator="@animator/click_animator"
            android:text="animation"/>
...

gif

stateに合わせてObjectAnimatorを指定するだけで、簡単なアニメーションができました。

複数のStateの組み合わせ

Stateは組み合わせることもできます。
分かりやすい所で、CheckBoxに適応することで、ON->OFFとOFF->ONのアニメーションを変えてみます。

check_box_animator.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 押した時は共通 -->
    <item android:state_pressed="true">
        <set>
            <objectAnimator
                android:duration="150"
                android:propertyName="scaleX"
                android:valueTo="0.5f" />
            <objectAnimator
                android:duration="150"
                android:propertyName="scaleY"
                android:valueTo="0.5f" />
        </set>
    </item>

    <!-- OFF->ON -->
    <item
        android:state_checked="false"
        android:state_pressed="false">
        <set>
            <objectAnimator
                android:duration="150"
                android:propertyName="scaleX"
                android:valueTo="1.0f" />
            <objectAnimator
                android:duration="150"
                android:propertyName="scaleY"
                android:valueTo="1.0f" />
        </set>
    </item>

    <!-- ON->OFF -->
    <item
        android:state_checked="true"
        android:state_pressed="false">
        <set android:ordering="together">
            <objectAnimator android:duration="300">
                <propertyValuesHolder android:propertyName="scaleX">
                    <keyframe
                        android:fraction="0"
                        android:value="0.5f" />
                    <keyframe
                        android:fraction="0.65"
                        android:interpolator="@android:interpolator/decelerate_cubic"
                        android:value="1.5f" />
                    <keyframe
                        android:fraction="1"
                        android:interpolator="@android:interpolator/accelerate_cubic"
                        android:value="1.0f" />
                </propertyValuesHolder>
            </objectAnimator>
            <objectAnimator android:duration="300">
                <propertyValuesHolder android:propertyName="scaleY">
                    <keyframe
                        android:fraction="0"
                        android:value="0.5f" />
                    <keyframe
                        android:fraction="0.65"
                        android:interpolator="@android:interpolator/decelerate_cubic"
                        android:value="1.5f" />
                    <keyframe
                        android:fraction="1"
                        android:interpolator="@android:interpolator/accelerate_cubic"
                        android:value="1.0f" />
                </propertyValuesHolder>
            </objectAnimator>
        </set>
    </item>
</selector>

gif

少々stateがどちらかを把握し難いですが、OFF->ONの方がバウンドするようなアニメーションに変更できました。

使用可能なStateは決まっています。
こちらに一覧がありますので、参考にどうぞ。
- StateList

ViewにStateを増やしてみる

前述の例で使っているandroid:state_checkedは、Checkableインターフェイスを実装したViewで参照可能なStateです。
CheckBoxはこれを実装しており、ソース上でも isChecked等のメソッドが使えるので、そちらで把握してる人の方が多いと思います。
つまるところ、Checkableインターフェイスを実装してしまえば、あらゆるViewで前述のような組み合わせのStateが使えます。

Checkableインターフェイスの実装についてはこちらの記事などが分かりやすいので、ここでは割愛します。
- http://y-anz-m.blogspot.com/2012/02/drawablestate-viewgroup.html
- https://qiita.com/muran001/items/047b0e5ba9e10f4846ae

例として、FloatingActionButtonを以下のように拡張して、同様のAnimatorを適応してみました。

ExFab.kt

class ExFab @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FloatingActionButton(context, attrs, defStyleAttr), Checkable {
    private var isChecked: Boolean = false

    init {
        setImageResource(R.drawable.ic_favorite_off)
        stateListAnimator = AnimatorInflater.loadStateListAnimator(context, R.animator.check_box_animator)
    }

    override fun isChecked(): Boolean {
        return isChecked
    }

    override fun toggle() {
        setChecked(isChecked.not())
    }

    override fun setChecked(checked: Boolean) {
        if (isChecked != checked) {
            isChecked = checked
            refreshDrawableState()
            setImageResource(if (checked) R.drawable.ic_favorite else R.drawable.ic_favorite_off)
        }
    }

    // Checkable実装に必要な処理
    override fun onCreateDrawableState(extraSpace: Int): IntArray? {
        val drawableState = super.onCreateDrawableState(extraSpace + 1)
        if (isChecked()) {
            View.mergeDrawableStates(drawableState, CHECKED_STATE_SET)
        }
        return drawableState
    }

    companion object {
        private val CHECKED_STATE_SET = intArrayOf(android.R.attr.state_checked)
    }
}

gif

CheckBoxの様に動き、各Stateでアニメーションが変わるFabを実装できました。

アニメーションのカスタマイズ

アニメーション内容はobjectAnimatorの範囲でカスタマイズ可能です。
前述の例で既に使っていますが、keyframeなどを使う事により細かいアニメーションも作れます。

gif gif
(これらは一例であり、この様なアニメーションが良いかと言われると微妙ではありますが…。)
ドキュメントはこちらにありますので、これを参考にカスタマイズしてみると良いでしょう。
- https://developer.android.com/guide/topics/graphics/prop-animation#declaring-xml

終わりに

設計思想によるかもしれませんが、View単体で単純なアニメーションが完結できると、使い回す上でかなり便利かと思います。

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

showModalBottomSheetで角が丸いContainerを表示する【Flutter】

はじめに

showModalBottomSheet()というメソッドを利用することで、「下からニョキっと出てくるウインドウ」のようなものを作ることができる。

今回は、それを使って「角が丸いContainer」を表示する。
以下の画像のような感じ。

image.png

上辺の両端を見ていただけたらお分かりの通り、角が丸い。
角を丸くするためにはRoundedRectangleBorderウィジェットを利用するのだが、普通に使うとContainerとの相性が悪いようである。
それについては、後で解説する。

コード

では早速、コードを全文載せておく。

main.dart
import 'package:flutter/material.dart';

void main() => runApp(MaterialApp(
      home: MyApp(),
    ));

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          child: Text('Push'),
          onPressed: () {
            showModalBottomSheet(
                context: context,
                isScrollControlled: true,   //trueにしないと、Containerのheightが反映されない
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.vertical(top: Radius.circular(15)),
                ),
                builder: (BuildContext context) {
                  return Container(
                    height: 540,
                  );
                });
          },
        ),
      ),
    );
  }
}

showModalBottomSheetのプロパティとして、isScrollControlledとshapeを指定している。
それ以外は、いたって単純。
isScrollControlledはtrueに設定しないと、Containerのheight設定が反映されないので、注意。

これで、ボタンを押すと画像のようなウインドウが表示されるアプリができた。

難点

Containerの色を変更したいときはどうすればよいのだろうか。
実はContainer内でcolorプロパティを変更しても、うまくいかない。

試しにContainerウィジェット内をこう書き変えてみてほしい。

                  return Container(
                    height: 540,
                    color: Colors.red,
                  );

colorプロパティにColors.redを指定して、赤色にしようとしている。
実行すると、こうなる。

image.png

角が四角い!!

これはガッカリした。
どうやら角を丸くしつつ、色を変更するためには、ContainerではなくshowModalBottomSheetそのものの引数に渡さないといけないみたい。

というわけで、showModalBottomSheet内をこう書き変えると、うまくいく。

            showModalBottomSheet(
                context: context,
                isScrollControlled: true,
                backgroundColor: Colors.red,   //これを追加した
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.vertical(top: Radius.circular(15)),
                ),
                builder: (BuildContext context) {
                  return Container(
                    height: 540,
                  );
                });

実行すると、こうなる。

image.png

これで一件落着!

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

【個人メモ】個人的にAndroidアプリ開発をするときに役立った記事一覧

はじめに

この記事は、個人的にAndroidアプリ開発をした際に役立った記事についてまとめた個人的なメモです。
ですので、その点を踏まえて閲覧していただきますようお願いいたします。

Fragmentが重なる

BottomNavigationViewがキーボードの上に表示されてしまう

キーボードを隠したい

隠れるHeader

  • 要点まとめ
    • 以下のコードのように、CoordinatorLayout内にHeaderとなるAppBarLayoutとコンテンツを作成
    • AppBarLayout内にCollapsingToolbarLayoutを作成、この中にHeaderに表示したいコンテンツとToolbarを作成
    • CollapsingToolbarLayoutのlayout_scrollFlagsによってスクロール時のHeaderの動作が変化する
    • Toolbarのlayout_collapseModeによって、Toolbarを上部に表示したままにできる
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context=".~~Activity">
    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/app_bar_height"
        android:fitsSystemWindows="true"
        android:theme="@style/AppTheme.AppBarOverlay">
        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:toolbarId="@+id/toolbar">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax"
                app:srcCompat="@drawable/ic_baseline_chat_24"/>
            <de.hdodenhof.circleimageview.CircleImageView
                android:id="@+id/fab"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_anchor="@id/app_bar"
                android:src="@mipmap/icon_180"
                android:layout_marginTop="100dp"
                android:layout_marginLeft="10dp"/>

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin">
                <TextView
                    android:id="@+id/titleText"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textSize="18sp"
                    android:textStyle="bold"
                    android:textColor="@color/white"
                    android:text="Title"/>
            </androidx.appcompat.widget.Toolbar>
        </com.google.android.material.appbar.CollapsingToolbarLayout>
    </com.google.android.material.appbar.AppBarLayout>
    <include layout="@layout/content_scrolling" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firebaseを利用したAndroidアプリをリリースしたお話

はじめに

初めてAndroidの個人アプリをリリースしたことを契機に、Qiita始めてみました。
この記事では、以下についてお話します。

  • どんなアプリをリリースしたか
  • リリースまでの稼働時間は?
  • Androidアプリ開発に活用したサービスやツール
  • Firebaseを採用する前に知っておきたかったこと
  • その他、反省点

どんな個人アプリをリリースした?

冒頭に、作ったアプリの宣伝だけさせてください...!
匿名で質問を投稿できるアンケートアプリ「Anshare」をリリースしました:raised_hands:
ぜひ使ってみてください。そして辛辣なご意見お待ちしてます。

アプリの機能

  • ユーザは、2~4択の質問を投稿できる
    • 質問の投稿者は、回答の統計データを知ることができる
      • 回答の各割合
      • 回答したユーザの年齢層
      • 回答したユーザの地域
  • ユーザは、質問に対して各ユーザは1回だけ回答できる
    • 回答者は、回答の各割合を知ることができる

アプリのイメージ

こんな感じの画面、挙動です!!

投稿画面 回答画面 集計画面1 集計画面2 集計画面3
Anshare投稿画面 Anshare回答画面 Anshare集計結果画面の回答別 Anshare集計結果画面の年齢別 Anshare集計結果画面の地域別

利用したFirebaseプロダクト

  • Authentication
  • Firestore
  • Functions
  • Remote Config
  • Cloud Messaging
  • Crashlytics
  • AdMob

リリースまでの稼働時間は?

2020年から着手して、リリースまで1年2ヵ月掛かりました。
土日の隙間時間でコツコツという感じです。(2020年の8月から年末まで別の開発に浮気してたりしてます(笑)
実質7~8ヵ月といったところでしょうか。

2020 2021
contributions_in_2020.PNG contributions_in_2021.PNG

Androidアプリ開発に活用したサービスやツール

個人アプリ開発で活用したサービスやツールをご紹介。(githubやandroid studioに付随するツール、adb等は省きました。)
僕が知らないだけで、世の中にはもっと作業効率を向上させるアイテムがあるはず。
(色々コメントで教えていただけると嬉しいです!)

サービス / ツール 利用用途 イメージ
Adobe XD UI/UXデザインツール。
僕は画面構成だけでなく、サービスアイコンも全てXDで作りました(笑)
xd-image
Draw.io フローチャートやシーケンス図をオンライン上で作成できるサービス。
情報整理に最適。
僕は処理フローの整理(右の画像が実際のフローの1部)やFirestoreのデータ設計等で利用しました。
draw-io-image

Firebaseを採用する前に知っておきたかったこと

Firestoreの検索機能は弱い

Firestoreをデータストアとして利用しようと考えておられる方、こちら要注意です。
例えば、以下のような条件でデータを取得することができません。

  • 自分(userA)が投稿したデータでない
  • 3/2以降のデータ
// 下記のようなドキュメント構成を仮定
{
  title: '質問'
  category: 'game',
  uid: 'testUser'
  createdAt: '2021/3/3 12:00:00'
}
// Selectしてみる
mFirestore.collection("questions")
                .whereNotEqualTo("uid", 'userA')
                .whereGreaterThan("createdAt", '2021/3/2 00:00:00')
                .get();
// エラー...。
2021-03-03 22:59:49.807 25606-25606/com.crabsan.anshare.debug W/xxx: onError, Firestore select error.
    java.lang.IllegalArgumentException: All where filters with an inequality (notEqualTo, notIn, lessThan, lessThanOrEqualTo, greaterThan, or greaterThanOrEqualTo) must be on the same field. But you have filters on 'uid' and 'createdAt'
        at com.google.firebase.firestore.Query.validateNewFilter(Query.java:561)
        at com.google.firebase.firestore.Query.whereHelper(Query.java:431)
        at com.google.firebase.firestore.Query.whereGreaterThan(Query.java:204)

そうなんです。Firestoreでnot検索をする場合、複数項目を検索条件に持つことはできません:sob:
この仕様を把握できていなかったので、色々と仕様を変更して制約とするハメになりました...。
他にも制約があったりするので、採用前は念入りに確認をオススメします。

クエリの制限事項

複合クエリにおいて、範囲(<、<=、>、>=)と不等値(!=)の比較は、すべて同一のフィールドに対するフィルタである必要があります。

改善策は、AlgoliaElasticSearchといった別サービスと組み合わせる必要があるそうです。※1
お金を極力かけたくなかったため、こちらは断念しました。

Firestoreの読み取り回数問題

例えば、下記 answerd_users サブコレクションに入っているドキュメント数を取得したい場合です。
ドキュメント数を取得するにはサブコレクション配下を全件取得するしか方法がなく、読み取り回数は4requestとなります。1requestではないのです。
そのため、データ増加に沿ってリニア的に読み取り回数が増えることに...:crying_cat_face:

対応策としてはサブコレクション自体にカウンターを持たせて対応しました。
ドキュメント追加時にカウンターをインクリメントすることで書き込み回数は増えますが、データ数取得時は1requestだけとなるためトータルで見ると読取コストを削減することができます:middle_finger:

修正前 修正後
firestore-caution-image1 firestore-caution-image2

利用料金の上限リミッターを設定できない

FirestoreやFunctions等は、無料枠はありますが利用した分だけ青天井でお金を支払わないといけません。
プラットフォーム側で上限を設定可能と思っていたが、出来ないのです。
課金額が閾値を超えたらアラートを送信する機能はありましたが、機能を制限してを課金を止めるリミッターはありませんでした。
Firestore宛にメールで問い合わせてみましたが、結果は同じで要約すると以下のような回答でした。

  • 制限機能は無い
  • Firestoreのベストプラクティスの推奨事項に従うことをオススメする

対応策として、RemoteConfig を利用してアプリをメンテナンスモード(起動不可制御)に変更できるような機能追加をしました。
リリース後、収入 < 支出となった場合にはこの機能を活用してサービス停止するかもしれないです。(弱気

firestore-support-contact

その他の反省点

デザイン・機能を固めてから開発すべし

Adobe XDでぼんやりUIを作って、後は開発しながら考えようと始めた結果、無駄な開発、修正をすることが多かったです。
事前にガッチリ計画する時間を取った方が、必要工数は間違いなく短縮できたと思ってます。

下図は当初作ったUI/UXの1部。
画像の2択アプリを想定して投稿機能まで実際に開発したのですが、予算や休日1人で運用することを考えたら厳しいと実感して断念。今リリースした内容に方向転換しました。次は失敗しないぞ:innocent:
adobe-xd-img

KotlinやJetpack等、Android純ライブラリをもっと活用すべきだった

JavaオンリーでRxjava、butterknife等の外部ライブラリを活用しつつ開発していたのですが、下記のようなGoogleが用意してくれているライブラリで代替できる場合が多かった。
時代に取り残されている感がすごいので、次は積極的に使っていきたい。

  • Kotlin(コルーチン)
  • hilt
  • databinding
  • viewmodel
  • livedata
  • lifecycle

まとめ

リリースしたアプリは全てFirebase内に完結しており、Firebaseはとても便利であることを実感。
けれども、しっかりと事前調査しておかないと僕の二の舞になるということだけ覚えていただければと思います...!
他の個人アプリを作る機会があれば、この失敗を糧にしていきたい:muscle:

※ これを契機にTwitter始めてみました。横の繋がりを増やしていきたいのでフォローやコメントお待ちしてますw
https://twitter.com/takashikatt

参考にさせていただいたドキュメント

※1 https://qiita.com/1amageek/items/d606dcee9fbcf21eeec6

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