課金をAIDLからBilling Libraryに変更した話

以下を参考に実装した。

バージョンとコード

implementation ‘com.android.billingclient:billing:1.2’

感想

AIDLファイルを残したままにすると、デバッグビルドは通るがリリースビルドでのみこけるのに多少ハマったけど、それ以外はすごく使いやすくなっている。developerPayloadがなくなっているので使う前提のサービスだと変更はできないが、developerPayloadはdeprecatedにまではなっていないが、将来的に消えるらしいのでdeveloperPayloadに依存しないアプリを作ったほうが良い。

また、1.1から1.2へのアップデートでBillingFlowParamsのsetSku / setTypeがdeprecatedになっており、setSkuDetailsを使うことが推奨されている。引数として使用されるSkuDetailsのconstructorはjsonSkuDetailsのStringを設定する口しかなくBuilderも存在しないため自力で作ることが困難な作りになっている。GoogleとしてはquerySkuDetailsAsyncでSkuDetailsを取得したものを使用して課金処理を実行してほしいのだろう。SkuDetailsにはストアで設定したタイトルや詳細が取得できるので、これを使用してUIを作り保持しているSkuDetailsを使用してそのまま課金処理に移ってほしいという流れなのだろうと想像している。PlayStoreでの課金アイテムを変更するだけでアプリをアップデートすることなく課金アイテムを変更できるという利点もあるわけだが、タイトルにアプリ名が含まれているしストアで設定した文言がそのまま表示されるわけではないのであまり使い勝手は良くない印象を受けた。アップグレード、ダウングレードもsetOldSkuを設定するだけで対応できるのは非常にありがたい。

サービスで使用する場合はstartConnectionのエラー時に再接続処理を走らせたり、課金アイテムの消費タイミングを変更したり、課金完了時にレシート情報をサーバに送信しサーバ側で有効なレシートかチェックしたりと色々やる必要はあるが、クライアント側の処理は非常に簡潔になりすごくよい。AIDLで消費する前にBillingLibraryを使用したほうがなにかと捗りそうだ。

DataBindingでSpannableStringを使いたい

TextViewの装飾でSpannableStringを使っているのに、ObservableField()を使って値を設定すると装飾が消える。

ObservableFiled()を使って設定すれば装飾が残るのでこっちを使おう。

RealmにEnumを保存したい

RealmはEnumを保存できない。なので、nameを保存してそれを使ってenumが引ける口を作ると簡単だと思う。

WorkerManager#beginUniqueWork で複数登録する場合はDelayを入れたほうが良い

WorkerManagerのbeginUniqueWorkでWorkerを大量に突っ込むと3つ目で以下のエラーを吐いて落ちる

E/WorkerWrapper: Status for aa9f2b0d-d327-4a9f-bac1-5f3e6799603a is ENQUEUED; not doing any work

以下のようにちょっとだけDelayをいれると動いた。

うまいことWorkerをまとめる術があるのなら他にやったほうが良さそうだが、beginUniqueWorkでまとめる場合はDelayをいれたほうが無難だと思われる。

Google Sign-InでApiException: 12501がでて失敗する

以下のエラーがでて失敗する

> com.google.android.gms.common.api.ApiException 12501

手順は以下の通り

OAuth2のClientKeyが設定されてないのがだめというものを見つけたけど違った。

結局、 GoogleSignInOptions.DEFAULT_SIGN_IN を設定しないといけないところを GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN を設定していたからエラーとなっていた。

うまくいったコード

バージョンは以下

> implementation “com.google.android.gms:play-services-auth:15.0.1”

FirabseのAuthUIで[Code: 10, message: 10]のエラーが出る

FirebaseのAuthUIを試してみたけど以下のエラーがでて動かない。

com.firebase.ui.auth.FirebaseUiException: Code: 10, message: 10:

よくよくみると以下のワーニングが出ていた

Developer error: this application is misconfigured. Check your SHA1 and package name in the Firebase console.

SH256のほうがいいだろうと256しか登録していなかったが、SHA1が登録していないとだめらしい。FirebaseConsoleの設定からSHA1を登録したところ問題なく動作した。

複数のFlowableで全ての更新通知で全ての値を結合する

結論を先に書くと Flowable.combineLatest を使うと良い

flatMapで結合

こうすると、onNextが流れるたびにobserveされる物が増えていくためintProcessorで流れてきた回数strProcessorが出力されることになる。一応目的は果たして入るが、同じ値が複数流れてくるので非常に効率が悪い。

combineLatestで結合

こうすると、どちらかのonNextが流れてきたときに、他の最新データを使って結合することができる。これがやりたかったこと。

TextViewのMarqueeが動かない問題に終止符を

TextViewのMarqueeが動かない問題でいつも悩んでいるので色々なところを参考にして以下のクラスを作った。これならどの場面でも動くことは動く。

以下が参考になる。

http://saways.blogspot.jp/2011/10/textview-marquee.html
 saways.blogspot.jp 

どちらも最終的な結論は「isFocuse()でtrueを返せ!」なんだけど、isFocuse()でtrueを返すとフォーカスがTextViewにあることになるのでキーボードが開けないという困ったことが起こる。なので、StackTraceからMarqueeに必要な関数から呼ばれているかを判定してtrueを返答するようにしてるんだろうけどこれだと関数名が変わったり、別の箇所から呼ばれたりとRefactorされただけで動かなくなってしまいそうで怖い。

実際にTextViewの中身を見ると && (isFocuse() || isSelected()) で判定をしているので、どちらかを返答すればよいのだからisSelected()でtrueを返答してあげたほうが実害が少ないのでこっちを採用することで僕の環境だと上手く動いた。XMLで色々やったりしてもどうしても動かない箇所が出てくるのでこれで動かすのが今のところのベスト。もし違う実装が見つかったらまた書くかも。

rxAndroidのsubscribeOnとobserveOnの違い

よくわからなかったので動かしながら試したけど結論は

スレッドを変えたかったらobserveOnを使え!

ってことだった。

お試し1

お試し2

解説

ログを取って確認してみると以下のように動いている。

subscribeOn
Observableの動き出しのスレッドを指定。最初に指定されたものが使われる。
observeOn
以降が動くスレッドを指定:

となっている。

僕の理解ではObservable.just()の部分もmainで動くのかと思ったけど、Observable.create()からsubscribeOnで指定されたスレッドで動くっぽいように見える。subscribeOnはLibraryとか配るときにObservable.create()を動かすスレッドを固定することが出来るっていうのが利点。ライブラリ作って配布するとかしないのであれば使う必要なくてスレッドを変えたいだけならobserveOnを使わなければならない。

(続)ListViewのEmptyViewが使えないので代替案を考える

これの続き

ViewTypeで分けてみる

前に作った方法でやるとどうしても無理矢理っぽい。なので、ViewTypeにEmptyを追加して0件なら表示するように変更してみた。

public class ListAdapter extends ArrayAdapter {

    // 表示するViewType達
    private enum ViewType {
        NORMAL(0),
        EMPTY(1);

        private int key;

        ViewType(int key) {
            this.key = key;
        }

        public int getKey() {
            return key;
        }

        public static ViewType getByKey(int key) {
            ViewType ret = null;
            for (ViewType viewType : values()) {
                if (key == viewType.getKey()) {
                    ret = viewType;
                    break;
                }
            }
            return ret;
        }
    }

    @Override
    public boolean isEmpty() {
        return (super.getCount() == 0 && mEmptyView == null) ? true : super.isEmpty();
    }

    @Override
    public int getCount() {
        // 0件かつEmptyViewが設定されていたらEmptyView分の1を返答する
        return (super.getCount() == 0 && mEmptyView != null) ? 1 : super.getCount();
    }

    @Override
    public int getViewTypeCount() {
        return ViewType.values().length;
    }

    @Override
    public int getItemViewType(int position) {
        // 0件かつEmptyViewが設定されていたらEmptyViewになる
        return (position == 0 && super.getCount() == 0 && mEmptyView != null) ?
                ViewType.EMPTY.getKey() : ViewType.NORMAL.getKey();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view;

        switch (ViewType.getByKey(getItemViewType(position))) {
            case NORMAL:
                view = getNormalView(position, convertView, parent);
                break;
            default:
                // defaultになることはない。
            case EMPTY:
                view = getEmptyView(position, convertView, parent);
                break;
        }

        return view;
    }

    private View getEmptyView(int position, View convertView, ViewGroup parent) {
        // ここでEmptyViewを作成する
        return view;
    }

    private View getNormalView(int position, View convertView, ViewGroup parent) {
        // ここで通常のViewを作成する
        return view;
    }
}

まとめ

前回よりもこっちのほうがいいと思う。convertViewで再利用もちゃんとできるしいい感じ。setEmptyViewとかも合わせてOverrideした方がいいかも。