【AndroidStudio 3.0】「flavor dimension」とは何?

AndroidStudio 3.0 にアップデートしましたが,

こんな build.gradle で 以下のエラーです.


android {
  // ...
}

productFlavors {
  flavor1 {
    // ...
  }
}

buildTypes {
  release {
    // ...
  }
  debug {
    // ...
  }
}


Error:All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com/r/tools/flavorDimensions-missing-error-message.html

StackOverflowで調べてみると以下の記述で大丈夫と書かれている.

Android Studio 3.0 Flavor Dimension Issue - Stack Overflow


android {
  // ...

  flavorDimensions "default" // OK
  //flavorDimensions "versionCode" // OK

}

productFlavors {
  flavor1 {
    // ...
  }
}

buildTypes {
  release {
    // ...
  }
  debug {
    // ...
  }
}

これでビルドは通る.

なんだか気持ち悪いし意味が不明すぎますね.

 

試してみる

いろいろやってみましたが, 以下のような記述がOKです.


android {
  // ...

  flavorDimensions "a"
}

productFlavors {
  flavor1 {
    // ...
  }
}

buildTypes {
  release {
    // ...
  }
  debug {
    // ...
  }
}

productFlavor を複数にする.


android {
  // ...

  flavorDimensions "a"

}

productFlavors {

  flavor1 {
    // ...
  }

  flavor2 {
    // ...
  }

  flavor3 {
    // ...
  }

  flavor4 {
    // ...
  }

}

buildTypes {
  release {
    // ...
  }
  debug {
    // ...
  }
}

flavorDimensions で productFlavor を2段階に分ける.


android {
  // ...
  flavorDimensions "a", "b"
}

productFlavors {

  flavor1 {
    dimension "a"
    // ...
  }

  flavor2 {
    dimension "a"
    // ...
  }

  flavor3 {
    dimension "b"
    // ...
  }

  flavor4 {
    dimension "b"
    // ...
  }
}

buildTypes {
  release {
    // ...
  }
  debug {
    // ...
  }
}

dimension で階層化された Build Variant が作成される.

 

まとめ

もともと productFlavor では以下のようなものが設定できました.

- applicationId
- versionCode
- minSdkVersion
- versionName/Suffix
- ソースコードやリソースの位置
- dependencies

これらを階層化させたい場合に使うのがいいように思います.

まずは, 開発時の時間短縮でしょうか.


flavorDimensions "minSdkVersion", "others"

Migrate to Android Plugin for Gradle 3.0.0 | Android Studio

ビルド バリアントの設定 | Android Studio


ログ出力をクリッカブルにして該当行に素早く移動

Android Studio でのログ出力.

任意の文字列を出力する場合は,


Log.d("TEST", "テスト")

と書いておくと,

となります.

Kotlin では,


println("TEST テスト")

と書いてあげると,

となります.

以下のような文字列にすると,


println("TEST https://google.com/")

println("TEST (MainActivity.kt:100)")

と表示されて,

それぞれブラウザの該当ページや,

該当ファイルの該当行に直接クリックで遷移できるようになります.

ここで,

Exception().stackTrace[1]

を使うと, 記述した位置の該当ファイル名と行数が取得できるので,

インラインなユーテリティにします.


class LogUtils {

  companion object {

    private const val TAG : String = "TEST"

    @JvmStatic fun d(message: String) = Log.d(TAG, appendFileLine(message))
    @JvmStatic fun d(message: String, t: Throwable) = Log.d(TAG, appendFileLine(message), t)

    @Suppress("NOTHING_TO_INLINE") // ?
    private inline fun appendFileLine(message: String): String {
      val frame = Exception().stackTrace[1]
      return "$message (${frame.fileName}:${frame.lineNumber})"
    }

  }

}

あとは, 表示したいメッセージとともにお好みの場所で


LogUtils.d("テスト")

と書いておけば,

となって便利にデバッグに使えたり.


Play Store の Google 取り分が30%から15%へ 2018年1月から

以前から噂はありました.

2016年6月9日

Recodeが伝えたところによると、Googleもまた新たな利益配分モデルを導入し、月額課金アプリでGoogleが吸い上げる割合を15%に削減する計画があるとのこと。しかもアップルは15%の適用を月額課金アプリの、しかも2年目以降からに限定するのに対して、Googleは1年目からこの利益配分を適用するとしています。

ただ、Recodeはこの新しい利益配分がいつから適用になるのかはまだわからないとしています。

GoogleもAndroidの月額課金アプリ取り分を15%に引き下げるという噂。しかも1年目から対象の可能性 - Engadget 日本版

ついに来年からのようです.

Now, Google is doing the same. Similar to Apple’s approach, an Android developer selling a subscription service will be eligible for the cut so long as the customer in question has been subscribed for more than a year. The company plans to put it into effect starting January 2018.

今回 Googleは同様のことをする予定です。アップルのアプローチと同様に、サブスクリプションサービスを販売しているAndroidデベロッパーは、問題の顧客が1年以上契約している限り、そのカットを受けることができます。同社は、2018年1月から施行する予定です。

Google matches Apple by reducing Play Store fee for Android app subscriptions - The Verge

英語ページでは公式でも追記されています.

Appleと同様に2年目からの定期課金加入者のみのようです.

Note about subscription payments: Starting January 1, 2018, the transaction fee for any subscribers you retain after 12 paid months will be 15% (instead of 30%).

Transaction fees - Play Console Help

なぜか日本語ページではまだ記述がありません.

取引手数料 - Play Console ヘルプ

もしすると, 日本はまだ先になるのかもしれませんが開発者にとってはいい話でしょう.


「Realm Report」にみるモバイル開発のトレンド

Realm Report Q4 2017

モバイル開発者が利用している言語は?

モダンな言語に移行が進んでいる

今現在, モバイルプラットフォームでは7つの主要言語がありますが割合は急速に変化しています. iOS開発者においては, Swift が登場しましたが, 今では Objective-C を超えています.

Androidでは, Javaがピークに達しており Kotlin のAndroidプラットフォーム獲得が進んでいます.

 

世界で一番モバイル開発の進んでいるのは?

優秀な開発者はどこにでもいる

Realm を利用した開発者は基本的なデフォルトの選択を超えており, 193カ国からコードがプッシュされていますが, 上位10カ国は世界中に均等に分布しています.

現在, 開発者が注目している技術やプラットフォームの興味深い違いをこの分析に見ることができます.

 

iOSは西欧を拠点としている

西欧の開発者は iOS 11 beta にすばやく注目しており, 全開発者の10%以上が, ドイツとアメリカにおいて iOS11beta を利用しています.

 

西欧の開発者は iOS の新技術に素早く対応している

西欧諸国の開発者は新しい Realm の iOS バージョンに非常に早くアップグレードしています.

 

西欧の開発者はiOS8のような古いバージョンは素早く切り捨てています

新しい技術に対応することは, 古いバージョンやデバイスのサポートをやめることになることになることがあります. 西欧諸国では, そのトレードオフのほうを好んでいます,

 

iOSに関しては, ドイツとアメリカが常に上位 (イギリスが3位)

iOS11対応やRealm対応, 古いiOSバージョンを切り捨てるスピードでは3カ国の開発者は他を圧倒しています. イギリスは開発者数では12番目ですが, 対応の速さでは常にドイツとアメリカに続いています.

 

Androidではアジアがリードしている

iOS では, アプリビルド数ではアメリカが上位ですが, Android Oreo 対応では低迷しており, ロシアや日本が上位となっています.

 

Android版Realmの対応割合の上位ははっきりしない

Android版Realmの対応状況はアジア諸国の開発者は新技術を追うことに対してより保守的であることを示しています.

 

Kotlin 対応はアメリカがさらに加速していくかも知れない

Android に Kotlin が新しい風を起こしており, アメリカ開発者のプラットフォームの将来を動かすかもしれない.

 

モバイル開発全体ではドイツが明快にトップ

ドイツのモバイル開発が両プラットフォームでのトップで Android とiOS の新バージョンや技術の対応も最も素早い.

 

Kotlin が今 Android 生態系を変えようとしている

Google I/O 2017 以降, Kotlin の割合は劇的に世界中で増加しています.

 

まとめ?

iPhoneXは4Kテレビと同じにおいを感じるんですよ。「技術としてスゴい」ことと「便利」なことって似ているようで違いますよね。んで、iPhoneXは、技術を追求するために便利さを捨てている気がするんですよ。

ホリエモン×ひろゆきが分析するiPhoneXの新機能「技術を追求するために便利さを捨てている」 - IT・テクノロジー - ニュース|週プレNEWS[週刊プレイボーイのニュースサイト]

アジアでありながら西欧諸国のようにiPhoneの販売台数割合は大きい日本ですが, 今後, 「古いOSやデバイスを考慮しない」という考え方でそれらを切り捨てていくという西欧スタイルの考え方は今後もさらに浸透していくのでしょうか.

そして, 新しい技術への対応や古いOSを切り捨てることに対してのより保守的なアジアスタイルの考え方はやは今後も縮小していくのでしょうか.

Realm Report Q4 2017


Dagger に馴染めない人のためのいくつかの原則

Keeping the Daggers Sharp ⚔️ – Square Corner Blog – Medium

Dagger2 は 素晴らしい Dependency Injection ライブラリですが, なかなか上手に使いこなせません.

分かりやすくするための考え方や実装方法をいくつか見てみましょう.

フィールドよりコンストラクタのインジェクションを使う

フィールドインジェクションは, finalでなく, privateでないフィールドに使います.


// BAD
class CardConverter {

  @Inject PublicKeyManager publicKeyManager;

  @Inject public CardConverter() {}

}

フィールドに @Inject を忘れると NullPointerException の原因となります.


// BAD
class CardConverter {

  @Inject PublicKeyManager publicKeyManager;
  Analytics analytics; // Oops, forgot to @Inject

  @Inject public CardConverter() {}

}

コンストラクタインジェクションはイミュータブルですので, 局所的な状態を持ちませんのでスレッドセーフにつながります.


// GOOD
class CardConverter {

  private final PublicKeyManager publicKeyManager;

  @Inject public CardConverter(PublicKeyManager publicKeyManager) {
    this.publicKeyManager = publicKeyManager;
  }

}

Kotlinでは, さらに簡素化してくれます.


class CardConverter

@Inject constructor(
  private val publicKeyManager: PublicKeyManager)

それでも, フィールドインジェクションを使いたい場合は以下のようになります.


public class MainActivity extends Activity {

  public interface Component {
    void inject(MainActivity activity);
  }

  @Inject ToastFactory toastFactory;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Component component = SquareApplication.component(this);
    component.inject(this);
  }

}

Singleton はあまり使う必要はない

ミュータブルな状態に対して多くのアクセスが必要な場合は Singleton は便利です.


// GOOD
@Singleton
public class BadgeCounter {

  public final Observable<Integer> badgeCount;

  @Inject public BadgeCounter(...) {
     badgeCount = ...
  }

}

しかし, 変化しない状態に対しては Singleton にする必要はありません.


// BAD, should not be a singleton!
@Singleton
class RealToastFactory implements ToastFactory {

  private final Application context;

  @Inject public RealToastFactory(Application context) {
    this.context = context;
  }

  @Override public Toast makeText(int resId, int duration) {
    return Toast.makeText(context, resId, duration);
  }

}

まれに, 作成のコストがかかるキャッシュインスタンスの走査に使うことがあります. そうすることで, 繰り返し作成して破棄することを避けることができます.

@Provides でなく @Inject を使う

@Provides はコンストラクタを複製すべきではありません.
関係する部分を一つにするとコードがわかりやすくなります.


@Module
class ToastModule {

  // BAD, remove this binding and add @Inject to RealToastFactory
  @Provides RealToastFactory realToastFactory(Application context) {
    return new RealToastFactory(context);
  }

}

このことは, 特に singleton においては重要です. そのクラスを読むときの重要な実装の内容一覧となります. 一部分をみればすべての内容が把握できます.


// GOOD, I have all the details I need in one place.
@Singleton
public class BadgeCounter {

  @Inject public BadgeCounter(...) {}

}

static @Provides を使う

@Provides は static にすることができます.


@Module
class ToastModule {

  @Provides
  static ToastFactory toastFactory(RealToastFactory factory) {
    return factory;
  }

}

モジュールインスタンスを作成する代わりに, 直接メソッドを実行することができます. このときそのメソッドの呼び出しはコンパイラによってインライン化されています.


@Generated
public final class DaggerAppComponent extends AppComponent {

  // ...
  @Override public ToastFactory toastFactory() {
    return ToastModule.toastFactory(realToastFactoryProvider.get())
  }

}

一つだけのメソッドを static にしてもあまり変化はないですが, すべてを static にすると, かなりのパフォーマンスが向上します.

また, モジュールを abstract にすると, static でない @provides メソッドが ひとつでもあるとコンパイルに失敗します.

@Provides よりも @Binds を使う

あるタイプを他にマッピングするときは @Provides でなく @Binds を使う.


@Module
abstract class ToastModule {

  @Binds
  abstract ToastFactory toastFactory(RealToastFactory factory);

}

このメソッドは abstract でなければなりません. @Generated コードは実装内容をそのまま使おうとします.


@Generated
public final class DaggerAppComponent extends AppComponent {

  // ...
  private DaggerAppComponent() {
    // ...
    this.toastFactoryProvider = (Provider) realToastFactoryProvider;
  }

  @Override public ToastFactory toastFactory() {
    return toastFactoryProvider.get();
  }

}

@Singleton の interface binding は避ける

Statefulness is an implementation detail

集中するアクセスがミュータブルな状態にアクセスする必要があるかは実装のみが知っていますので, 実装をインターフェースにバインドさせるとき, アノテーションをつけるべきではありません.


@Module
abstract class ToastModule {

  // BAD, remove @Singleton
  @Binds @Singleton

  abstract ToastFactory toastFactory(RealToastFactory factory);

}

error-prone を使おう

一般的な Dagger のエラーはこれを使うことで分かりやすく検出できます.

👉 MVVM で Hilt のパターン化 💉  hatena-bookmark