最新バージョンのライブラリがAndroidXのサポートライブラリに依存する

現在の導入手順に従って、最新のfirebase-coreのライブラリ

implementation 'com.google.firebase:firebase-core:17.0.0'

を入れると、利用しているSupport Library次第ではAndroidXのSupport Libraryが必要と叱られます。

manifest merger failed : attribute application@appcomponentfactory value=(android.support.v4.app.corecomponentfactory) from [com.android.support:support-compat:28.0.0] androidmanifest.xml:22:18-91

firebaseのライブラリに限らず、AndroidXのSupport Libraryに依存するライブラリバージョンが出始めていて、これからはAndroidXのSupport Libraryがメンテ対象となるようです。特別な理由がない限り利用するSupport LibraryをAndroidXに入れ替えたほうがいいと思います。

私の環境では以下の操作1つで簡単に入れ替えが出来ました。途中でZipのバックアップを取るか?と質問があるので、チェックをつけてバックアップを作成しておきましょう。

f:id:letitride:20190701112340p:plain:h400

また、理由があってAndroidXのSupport Libraryを導入できない場合は依存しているライブラリのバージョンを下げるとビルド出来る場合があります。

firebase-coreの場合、敢えて17.0.0を使わずに16.0.8を使用するとビルド可能となる場合があります。

implementation 'com.google.firebase:firebase-core:16.0.8'


The number of method references in a .dex file cannot exceed 64K.

開発中のビルドは問題なく通ってたのですが、ストア公開用の署名付きビルド作成時にこういったエラーが発生し、ビルドが失敗しました。

Error: null, Cannot fit requested classes in a single dex file (# methods: 71451 > 65536)
The number of method references in a .dex file cannot exceed 64K.

64Kを超えるメソッドを定義した時は特別な対応をする必要があるので、下記サイトの手順通りに対応を行います。また、64Kは導入したライブラリやSDKの中のメソッド数も含むので結構、頻繁に発生するのではないかと思います。

64K を超えるメソッドを使用するアプリ向けに multidex を有効化する  |  Android Developers

Application クラスをオーバーライドしない場合、次のようにマニフェスト ファイルを編集して、<application> タグで android:name を設定します。とありましたが、私の環境ではマニフェストに

<application
            android:name="android.support.multidex.MultiDexApplication" >

と記載したところ、unresolv multidex.MultiDexApplicationとなったので、マニフェストには特になにも記載せず、app/build.gradleのみに手順通り

android {
    compileSdkVersion 28
    defaultConfig {
        ...
        multiDexEnabled true
    }
}
...
dependencies {
    ...
    implementation 'com.android.support:multidex:1.0.3'
}

と記述して署名付きビルドしたところ問題なくビルドができ、Google Play Storeにて公開できました。


Androidアプリ FF14ブログアンテナアプリをリリースしました

FFXIVのブログアンテナアプリ、FF14タイムズをリリースしました。Android端末をお持ちのFF14プレイヤー様は攻略やプレイヤーの交流に是非、お役立て頂ければ幸いです。

play.google.com

同様のアンテナアプリはいくつかあって、電車の中等でよく利用するのですが、

  • 画面を覆う系の広告が多くて消すのが手間
  • 2chまとめ系のブログが主でプレイヤーブログが見られない

など少し不満があったので自分で作ってみました。

特にプレイヤーブログはドラマや映画にもなった「ひかりのお父さん」のブログ一撃確殺SS日記のようにみどころが多いブログが沢山あります。

sumimarudan.blog7.fc2.com

そこで、こういったプレイヤーブログを応援したい気持ちをこめて、ブログに対して当アプリ内のみで記録されるイイネボタンを設置しました。特に誰が押したなどは記録してないので、気軽に何回でも押せるようなボタンです。

開発期間

開発期間は7日間程度で、漆黒のヴィランズ、アーリーアクセス前にリリース目標で作業しましたが、半日ほどオーバーしてしまいました汗

搭載できなかった機能

心残りは、FF14といえばやはり「大縄跳び」と揶揄されることもあるエンドコンテンツの攻略ですが、攻略記事だけのコンテンツをアプリ内に設けようと検討していました。が、アーリーに合わせたリリース日程の関係で攻略記事特集は実装見合わせました。

iOSは?

また、iOS(iPhone/iPadアプリ)に関しては、開発者登録はしてるものの最近、こういった系統のアプリに対してappleの審査が厳しい(所謂、minimum functionaly)ようで、一旦、開発はペンディングしています。開発するとしたら8月くらいかな。といった具合です。

作者の近況

さて、自分のFF14の状況は?というと、少しばかりプレイから遠ざかっておりまして、ここだけの話し「まだ、紅蓮のメインが終わってない」といった半休止のような状態です笑。但し、きちんと月額のお布施は払い続けているので、8月くらいにはがっつり遊びたいなと考えています。

こういったアーキテクトを使っています

サーバ側API
  • PaaS : Heroku
  • PostgreSQL
  • Heroku Scheduler
  • Ruby on Rails
  • gem feedjira
  • gem kaminari
  • github
アプリネイティブ
  • 言語 : Kotlin
  • 非同期処理 : kotlinx-coroutines
  • 画像処理 : glide
  • OSSライセンス表示 : play-services-oss-licenses
  • method over 64K : multidex
  • アクセス解析 : firebase-core

ちなみにfirebase-coreは17.0.0からAndroidXのSupportLibraryに依存するようで、migrate to AndroidXを行うか、16.0.8にバージョンを落とすかといった対応が必要です。

コルーチンが最高に使い易かったです!

是非、Androidユーザ様は使って頂ければと思います!

VisibilityAwareImageButton.setVisibility can only be called from within the same library group

floatingActionButtonを

floatingActionButton.visibility = View.INVISIBLE

のようなことをすると、

VisibilityAwareImageButton.setVisibility can only be called from within the same library group (groupId=com.android.support) less... (⌘F1) 
Inspection info:This API has been flagged with a restriction that has not been met.  Examples of API restrictions: * Method can only be invoked by a subclass * Method can only be accessed from within the same library (defined by the Gradle library group id) * Method can only be accessed from tests.  You can add your own API restrictions with the @RestrictTo annotation.  Issue id: RestrictedApi

と叱られます。コード上からfloatingActionButtonの表示制御を行う場合、

floatingActionButton.show()
floatingActionButton.hide()

とするようです。


Android インターネット上の画像をImageViewで表示するのはGlideが便利

インターネット上に置かれている画像ファイルをダウンロードして表示するにはGlideが便利。

簡単な記述で接続からファイルダウンロード、描画、キャッシュの管理まで行ってくれます。

github.com

導入

app/build.gradle

implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'

利用例

以下のように簡単に利用できます。

Glide.with(context)
    .load("http://www.letride.jp/example.png")
    .fitCenter().into(WebView.image)


Android RecyclerView & CardViewのonClickイベントにハマる

昨日、RecyclerViewのクリックイベントがどうしても取得できずハマってしまったのでメモしておきます。

RecyclerView onClickイベントの基本系

Layout

RecyclerViewの中のitemを以下のようなレイアウトにした場合、

<LinearLayout 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:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">

    <android.support.v7.widget.CardView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/cardView">
    </android.support.v7.widget.CardView>
</LinearLayout>
onClickイベント

基本は RecyclerView.AdapterのonCreateViewHolder()の実装にonClickイベントを書くことになると思う。ネット上の情報もほぼこれ。

override fun onCreateViewHolder(parent: ViewGroup, p1: Int): ViewHolder {
    val itemView = LayoutInflater.from(parent.context).inflate(R.layoute.card_view_layout, parent,false)
    val holder = ViewHolder(itemView)
    //itemViewにリスナーを設定
    holder.itemView.setOnClickListener {
        //クリックされたViewHolderの位置が取得できる
        val position = holder.getAdapterPosition();
        doAction(piosition)
    }
    return holder
}

ただし、CardViewのクリック時に選択されたことを表現する為にandroid:clickable="true"を定義した場合、上記で言うitemViewのonClickイベントが取得できなくなります。

    <android.support.v7.widget.CardView
            android:clickable="true"
            android:foreground="?android:attr/selectableItemBackground"
            ...

CardView OnClickの定義

CardViewでリスナーの定義をするには単純に

override fun onCreateViewHolder(parent: ViewGroup, p1: Int): ViewHolder {
    val itemView = LayoutInflater.from(parent.context).inflate(R.layoute.card_view_layout, parent,false)
    val holder = ViewHolder(itemView)
    //CardViewにリスナーを設定
    holder.itemView.findViewById<CardView>(R.id.cardView).setOnClickListener {
        val position = holder.getAdapterPosition();
        doAction(piosition)
    }
    return holder
}

とすればよい。

蛇足 

上記のdoActionはAdapter内にベターっと書かずにActivityやFragment側で記述するのがシャレオツみたい。

interfaceを定義してうけとればよい。

Adapter

class FooAdapter( var mListener:onCardViewItemClick ): RecyclerView.Adapter<ViewHolder>(){
    interface onCardViewItemClick{
        fun onClick(position: Int)
    }
    override fun onCreateViewHolder(parent: ViewGroup, p1: Int): ViewHolder {
        ...中略
        holder.itemView.findViewById<CardView>(R.id.cardView).setOnClickListener {
            val position = holder.getAdapterPosition();
            mListener.onClick(piosition)
        }
        return holder
    }
}

Fragment側で実装を渡そう

class BarFragment : Fragment(),
    FooAdapter.onCardViewItemClick{
    override fun fun onClick(position: Int){
        //doAction
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...略
        recyclerView.adapter = FooAdapter(tihs)
    }
}


Android ViewのZ軸、重なりの高さを管理する

RecyclerView & CardViewの上にprogressBarを表示しようとした時、CardViewが最前面に表示されprogressBarがCardViewの下に潜ってしまうケースがある。

ViewのZ軸高さの設定

android:elevationでZ軸の高さを設定する。他Viewにandroid:elevationの設定がない場合、設定を行ったViewが最前面に表示される。

<ProgressBar
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:elevation="2dp">