ほげほげ(仮)

仮死状態

Dagger2メモ

Dagger2で最近知ったことのメモです。

バージョンは2.11-rc2

Component.Builder

Moduleが必要とするパラメータを渡して、Moduleのインスタンス化をDagger側でやってもらう。

AppComponent内に@Component.Builderをつけたinterfaceを作って、そこに使用してるModuleのパラメータを@BindsInstanceで指定してあげる。

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {

    @Component.Builder
    interface Builder {
        // Moduleが必要とするパラメータ
        @BindsInstance
        Builder application(Application application);

        // コレないとビルドエラーになる
        AppComponent build();
    }

    void inject(MainActivity activity);
}
@Module
public class AppModule {

    @Singleton
    @Provides
    HogeRepository provideHogeRepository(Application application) {
        return new HogeRepositoryImpl(application);
    }
}

AppComponentを取得するときに、new AppModuleをしなくてよくなり、そのかわり指定したパラメータを渡すようにする。

public class App extends Application {

    private AppComponent appComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        appComponent = DaggerAppComponent
                .builder()
                .application(this)  // AppComponentのBuilderに設定したメソッド
                .build();
    }

    public AppComponent getAppComponent() {
        return appComponent;
    }
}

Dagger2のAndroid拡張

なんか使い方が色々あってわからないこと多い。

とりあえず、一番シンプルな使い方。AndroidInjection.inject(this)が動くようにする。

まずはGradle。新しくAndroid拡張のものを追加します。

compile 'com.google.dagger:dagger:2.11-rc2'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11-rc2'
compile 'com.google.dagger:dagger-android:2.11-rc2'
compile 'com.google.dagger:dagger-android-support:2.11-rc2'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.11-rc2'

次にinjectをするActivityのModule作成。@ContributesAndroidInjectorというアノテーションをつけてinjectを行うActivityを指定する感じ。

@Module
public abstract class ActivityBuilderModule {

    @ContributesAndroidInjector
    abstract MainActivity contributeMainActivity();
}

AppComponentは新たに2つのModuleの指定を追加してます。AndroidSupportInjectionModuleAndroid拡張を使うために必要っぽい(よく分かってない)。いらないっぽい(よく分かってない)。もう一つは、先程のActivityBuilderModuleも追加してます。AppModuleはBuilderのとこで書いたものと同じです。

あとApplicationクラスを受け取るinjectメソッドを追加します。

@Singleton
@Component(modules = {AndroidSupportInjectionModule.class, AppModule.class, ActivityBuilderModule.class})
public interface AppComponent {

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(Application application);

        AppComponent build();
    }

    void inject(App app);
}

ApplicationクラスではHasActivityInjectorというinterfaceを実装して、DispatchingAndroidInjectorを返すメソッドを追加します。

で、そのDispatchingAndroidInjector自体の生成はDaggerに任せちゃう感じ、Applicationクラスでinjectします。

public class App extends Application implements HasActivityInjector {

    @Inject
    DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;

    @Override
    public void onCreate() {
        super.onCreate();

        DaggerAppComponent
                .builder()
                .application(this)
                .build()
                .inject(this);
    }

    @Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingAndroidInjector;
    }
}

Activity側はAndroidInjection.inject(this)を呼ぶだけです。 ドキュメントにはsuper.onCreate()の前に呼ぶようにと書いてます。試しにその後に書いても動いたのでよくわからない…

public class MainActivity extends AppCompatActivity {

    @Inject
    HogeRepository repository;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

まとめ

雰囲気でDagger2を使ってる

Android Architecture Components 感想

Google I/O 2017で追加された、Android Architecture Componentsを触ってみた感想です

Components

大きく分けて4つあります。

  • Lifecycle
  • LiveData
  • ViewModel
  • Room

Roomはちょっと時間かかりそうなので別の機会で。(やるかは未定)

とりあえず、Room以外の3つを触ってみました。

Lifecycle

これが一番重要だと思います。LiveDataもViewModelもこれがあるからこそ実現できるように感じました。

ActivityやFragmentのライフサイクルイベントの監視や状態の管理ができるようになります。

簡単な使い方です。

まずはLifecycleObserverをimplementsして、監視したいライフサイクルイベントをアノテーションで指定します。

public class SampleObserver implements LifecycleObserver {

    private static final String TAG = SampleObserver.class.getSimpleName();

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    void onCreate() {
        Log.d(TAG, "onCreate");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    void onStart() {
        Log.d(TAG, "onStart");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    void onResume() {
        Log.d(TAG, "onResume");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    void onPause() {
        Log.d(TAG, "onPause");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    void onStop() {
        Log.d(TAG, "onStart");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    void onDestroy() {
        Log.d(TAG, "onDestroy");
    }

}

Activity側です。いつもはAppCompatActivityを継承してると思いますが、LifecycleActivityってのを継承するようにします。

public class MainActivity extends LifecycleActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SampleObserver observer = new SampleObserver();
        getLifecycle().addObserver(observer);
    }
}

ポイントはgetLifecycle()LifecycleRegistryを取得して、addObserverで先程つくったObserverを追加してあげてます。

これで実行すると、Observerの各ライフサイクルイベントが呼ばれるのが確認できると思います。

ライフサイクルのイベントは2つ同時に指定できたり、引数でLifecycleOwnerとEventを受け取れたりもします。詳しくはドキュメントを見てください。

LiveData

変更通知してくれるデータです。RxJavaのPublishSubjectBehaviorSubjectに似てます。

単純な使い方

まずは単純な使い方です。

通知イベントを発火する側を作ります。

public class Sample {

    private final MutableLiveData<String> message = new MutableLiveData<>();

    public LiveData<String> getMessage() {
        return message;
    }

    public void doSomething() {
        message.setValue("Test");
    }

}

MutableLiveDataを定義して、それのsetValueを呼んであげると通知が送られます。通知を受け取る側は公開されてるLiveDataのほうを監視するようにします。

で、その監視する側です。

public class MainActivity extends LifecycleActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Sample sample = new Sample();
        sample.getMessage().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String message) {
                Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
            }
        });
    }
}

observeメソッドで監視しています。第一引数がLifecycleOwnerで、第二引数が通知を受け取るObserverになっています。

これの嬉しいところは、RxJavaであったようにunsubscribeの処理を自分で書く必要がありません。onDestoryが呼ばれるときに自動で削除してくれます。なので、メモリーリークする心配がありません。

データを加工する

Transformationsを使うと、データを加工することができます。

先程の例に少し手を加えます。mapメソッドで大文字に変換しています。

public class Sample {

    private final MutableLiveData<String> message = new MutableLiveData<>();

    public LiveData<String> getMessage() {
        return Transformations.map(message, new Function<String, String>() {
            @Override
            public String apply(String input) {
                // 大文字に変換
                return input.toUpperCase();
            }
        });
    }

    public void doSomething() {
        message.setValue("Test");
    }

}

もう一つが、switchMapってのがあります。これうまく説明できないのですが、LiveDataの値が変更されたときに、他のLiveDataを返すことができます。

public class Sample {

    private final MutableLiveData<String> message = new MutableLiveData<>();

    public LiveData<String> getMessage() {
        return Transformations.switchMap(message, new Function<String, LiveData<String>>() {
            @Override
            public LiveData<String> apply(String input) {
                // 別のLiveDataを返す
                return getOther(input);
            }
        });
    }

    private LiveData<String> getOther(String input) {
        return new LiveData<String>() {
            @Override
            protected void onActive() {
                // Activeになったらすぐに通知する
                setValue(input.toLowerCase());
            }
        };
    }

    public void doSomething() {
        message.setValue("Test");
    }

}

あまり例がよくないですが、messageの変更あったときに、getOtherっていうメソッドを経由して別のLiveDataを返すようにしています。

このあたりのほうが参考になるかもしれません。

LiveDataを継承

単純な良い例が思い浮かばかなかったので、省略。

ドキュメントにある、LocationManagerの例が分かりやすいのでそっちを見てください。

ViewModel

最後にViewModelです。

MVVMの文脈のViewModelとは大きく異るというか、実態は空っぽのonClearedだけが定義されてるabstractのクラスです。なのでMVVMアーキテクチャに対しては何もしてくれません。使い方次第です。

これは、回転とかでActivityが死んだ時にもデータを消えずに残してくことができます。

ぼくはMVVMを使っててよくViewModel(MVVM)の情報をonSaveInstanceStateで保持して、onCreateで復元するってのをよくやるんですけど、これが不要になってきます。

ドキュメントの下の図を見てもらえると生存期間が分かると思います。

単純な使い方

まずは単純な使い方です。

ViewModelを継承してonClearedをオーバーライドしただけのクラスです。

public class MainViewModel extends ViewModel {

    private static final String TAG = MainViewModel.class.getSimpleName();

    @Override
    protected void onCleared() {
        Log.d(TAG, "onCleared");
    }
}

次にActivity側です。ViewModelProviders経由でViewModelのインスタンスを生成してあげることで、ライフサイクルの管理を任せられます。自分でnewしても意味はないので注意が必要です。

public class MainActivity extends LifecycleActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MainViewModel viewModel = ViewModelProviders.of(this).get(MainViewModel.class);
    }
}

Contextがほしいとき

ViewModel内でContextがほしいときはAndroidViewModelを継承すると、コンストラクタで渡してもらえます。

コンストラクタに引数を渡したい

ViewModelに引数を渡したい場合はFactoryを作る必要があります。

先程のViewModel内にViewModelProvider.NewInstanceFactoryを継承したFactoryクラスを作ります。

そのFactoryでインスタンスを生成するようにします。

public class MainViewModel extends ViewModel {

    private static final String TAG = MainViewModel.class.getSimpleName();

    public MainViewModel(UserRepository userRepository) {
        // ...
    }

    @Override
    protected void onCleared() {
        Log.d(TAG, "onCleared");
    }

    public static class Factory extends ViewModelProvider.NewInstanceFactory {

        private UserRepository userRepository;

        public Factory(UserRepository userRepository) {
            this.userRepository = userRepository;
        }

        @Override
        public <T extends ViewModel> T create(Class<T> modelClass) {
            //noinspection unchecked
            return (T) new MainViewModel(userRepository);
        }
    }
}

Activity側は作ったFactoryを使うように指定してあげます。

public class MainActivity extends LifecycleActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MainViewModel viewModel = ViewModelProviders
                .of(this, new MainViewModel.Factory(new UserRepository()))
                .get(MainViewModel.class);
    }
}

まとめ

まだまだ、alph版なので変更はあると思いますが、ざっと触ってみた感じすごく期待できそうでした。

ぼくはMVVMで通知の仕組みをRxJavaのSubjectで実現させてるのですが、LiveDataで置き換えられるなぁって思ってます。

あとは、LifecycleのおかげでRxLifecycleもいらなくなってくる感じですかね。

おまけ

ぼんやりドキュメント眺めてたら、面白そうなクラスがありました。まだ実装は無いようです。

LiveDataReactiveStreams

どうなるか楽しみです。

Scala + Play Frameworkの環境構築メモ

Scalaの勉強しようと思って、せっかくだからPlay Frameworkも試そうとしたときのメモ。

調べるのに時間かかってるので、まだコード書けてない。

かなり雑だけど、自分用に残しておく。

Play Frameworkバージョン

  • 2.5.14

インストール

https://www.playframework.com/documentation/2.5.x/Installing

Java 1.8

Oracleからダウンロードしてインストール

http://www.oracle.com/technetwork/java/javase/downloads/index.html

sbt

Homebrew

$ brew install sbt

Application作成

$ sbt new playframework/play-scala-seed.g8

色々聞かれるけど、nameorganizationだけ設定して残りはEnterでデフォルトで。

name [play-scala-seed]: todo
organization [com.example]: com.star_zero
scala_version [2.11.11]:
scalatestplusplay_version [2.0.0]:
play_version [2.5.14]:

この場合は、todoって名前にしたのでtodoってフォルダができてる。targetってフォルダもできてるけどこれは何だろ?

カレントディレクトリを移動しておく

$ cd todo

起動

とりあえず、起動してみる

$ sbt run

結構な時間かかる。次のように表示されたら起動された。

--- (Running the application, auto-reloading is enabled) ---

[info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)

http://localhost:9000/ にアクセスしてみる

IntelliJ IDEAにインポート

基本的にここのとおり

インポートしたらまたしばらく時間かかる

ログ

conf/logback.xmlで設定

とりあえず、デバッグ用に<root level="WARN"><root level="DEBUG">にすると色々とログが出る。

DBとかORMとかMigrationとか

SlickじゃなくてScalikeJDBCが扱いやすいって教えてもらったので、そのようにしてる。実際ちょっと試した感じSlickはよく分からんかった…

ScalikeJDBC

DBアクセスライブラリ

http://scalikejdbc.org/documentation/playframework-support.html

PostgreSQLの場合

  • build.sbt
libraryDependencies ++= Seq(
  "org.postgresql" % "postgresql" % "42.0.0",
  "org.scalikejdbc" %% "scalikejdbc" % "2.5.2",
  "org.scalikejdbc" %% "scalikejdbc-config" % "2.5.2",
  "org.scalikejdbc" %% "scalikejdbc-play-initializer" % "2.5.1",
)
  • conf/application.conf
db.default.driver=org.postgresql.Driver
db.default.url="jdbc:postgresql://localhost/hoge"
db.default.username=hoge
db.default.password=hoge

scalikejdbc.global.loggingSQLAndTime.enabled=true
scalikejdbc.global.loggingSQLAndTime.singleLineMode=false
scalikejdbc.global.loggingSQLAndTime.logLevel=debug
scalikejdbc.global.loggingSQLAndTime.warningEnabled=true
scalikejdbc.global.loggingSQLAndTime.warningThresholdMillis=5
scalikejdbc.global.loggingSQLAndTime.warningLogLevel=warn

play.modules.enabled += "scalikejdbc.PlayModule"

SkinnyORM

ScalikeJDBCをActiveRecordっぽく使えるようにするやつ

http://skinny-framework.org/documentation/orm.html

  • build.sbt
libraryDependencies ++= Seq(
  "org.skinny-framework" %% "skinny-orm"      % "2.3.6",
  "ch.qos.logback"       %  "logback-classic" % "1.1.+"
)

Flyway

DB Migrationするやつ

https://flywaydb.org/

今のところはPlay用のプラグインあるけど使わない方向で。実行タイミングとかは自分でコントロールしたい感じなので。ただデプロイとか考えるとやっぱりプラグインとかになりそうな感じもある。

https://flywaydb.org/documentation/sbt/

  • project/plugins.sbt
addSbtPlugin("org.flywaydb" % "flyway-sbt" % "4.2.0")
resolvers += "Flyway" at "https://flywaydb.org/repo"
  • build.sbt

たぶん環境変数とかでやったほうが良いと思うけど、とりあえず。

flywayUrl := "jdbc:postgresql://localhost/hoge"
flywayUser := "hoge"
flywayPassword := "hoge"
flywayLocations := Seq("filesystem:conf/db/migration") // migrationファイルがある場所
  • migrationファイル

とりあえずサンプル。conf/db/migrationに配置。ファイル名はV1__Add_todo_table.sql

ファイル名の命名とかはここのNamingを参照。

CREATE TABLE todo (
  id VARCHAR(64) PRIMARY KEY,
  title VARCHAR(256) NOT NULL
);
  • 実行
$ sbt flywayMigrate

デプロイ

まだ調べられてない。後で調べる。

まとめ

初めてやることは色々と大変。

DroidKaigi 2017で発表してきた

MVVMについて話してきました。

このMVVMについて色々と議論はあるかと思いますが、まずはスタートラインに持っていけたかなと思ってます。これを元に色んな方が実践し議論してもっと良い方法を共有してくれるのを期待しています。今日も質問を受けたり議論もあって非常に勉強になりました。もっと改善できるようにこれからも色々模索していきます。

このような発表をすることはこれまでなかったので、かなり緊張してずっと寝不足気味な日々でした。ただ、終わってみるとやってよかった感は半端ないです。色んな人に声をかけてもらえて非常に嬉しかったです。ありがとうございました。

DroidKaigi関係者のみなさんに感謝!そして、相談に乗ってくれたり練習に付き合ってくれたりした同僚に感謝!

RxJavaのHotObservableのテストコード

HotObservableのテストコードを書く方法です。ちょっと調べたのですが、良い方法が見つからず自分なりに考えてみました。

もっと良い方法あったら教えてください。

環境

ごめんなさい、RxJava1です。RxJava2まだ追えてません。

対象コード

単純な足し算をして、それをHotObservableで通知するだけのものです。

これのテストコードを書きます。

public class Hoge {

    private final PublishSubject<Integer> subject = PublishSubject.create();
    public final Observable<Integer> observable = subject.asObservable();

    public void operation(int a, int b) {
        subject.onNext(a + b);
    }
}

TestSubscriber

RxJava関連のテストコードでよく使われると思いますが、TestSubscriberというのがあります。

これにawaitTerminalEventっていうメソッドがあって、onCompletedonErrorが呼ばれるまで待ちます。普通のColdなObservableとかはこれを使って処理が終わるのを待つことが可能ですが、HotなObservableの場合はonCompletedonErrorも呼ばれないので処理を待つことが難しいです。

awaitTerminalEventタイムアウトが設定できるので、とりあえずそれでお茶を濁すことも可能ですが、常にその時間待つことになります。

// 1秒でタイムアウト
// HotObservableの場合は毎回1秒待つ
testSubscriber.awaitTerminalEvent(1, TimeUnit.SECONDS);

なので、onNextが呼ばれたときにonCompletedを呼ぶように継承してHotObservable用のTestSubscriberを作ります。

ついでに呼ばれる回数も指定できるようにしてあります。

public class HotTestSubscriber<T> extends TestSubscriber<T> {

    private int count;

    public HotTestSubscriber(int count) {
        this.count = count;
    }

    @Override
    public void onNext(T t) {
        super.onNext(t);

        count--;

        // 指定回数呼ばれたらonCompletedを呼んで終了
        if (count == 0) {
            onCompleted();
        }
    }

    public static <T> HotTestSubscriber<T> create() {
        return new HotTestSubscriber<T>(1);
    }

    public static <T> HotTestSubscriber<T> create(int count) {
        return new HotTestSubscriber<T>(count);
    }
}

テストコード

先程の足し算のテストコードを書いてみます。

public class HogeTest {

    @Test
    public void operation() throws Exception {
        Hoge hoge = new Hoge();

        HotTestSubscriber<Integer> testSubscriber = HotTestSubscriber.create();
        hoge.observable.subscribe(testSubscriber);

        hoge.operation(1, 2);

        // 処理が終わるのを待つ(一応タイムアウトも設定しておく)
        testSubscriber.awaitTerminalEvent(1, TimeUnit.SECONDS);

        // onNextが呼ばれた回数を確認
        testSubscriber.assertValueCount(1);

        // onNextに入ってきた値を確認
        testSubscriber.assertValue(3);
    }
}

これでHotObservableもテストできるようになりました。

ObjectBox所感

greenrobotが新しいmobile object databaseのObjectBoxのBetaをリリースしたようです。

ObjectBox - The new Mobile Database (Beta) - Open Source by greenrobot

greenrobotは、EventBusgreenDAOを作ったとこです。

AndroidにおいてはRealmと競合になりそうな感じですかね。

機能

アナウンスの記事を見ると5つの機能をアピールしてますね。

  • Superfast
  • Object API
  • Instant unit testing
  • Simple threading
  • No manual schema migrations

ちょっと試してみた

ちょっと試して、Realmとの比較してみました。

セットアップは省略。

Entity

次のようなPOJOクラスを設定してアノテーションを指定します。とりあえず、書くのはこれだけです。

@Entity
public class Note {

    @Id
    private long id;

    private String text;

    private String comment;

    private Date date;
}

これをビルドすると、次のように勝手に色々生成されます。コンストラクタとgetter/setterですね。

f:id:STAR_ZERO:20170125180545g:plain

これはaptでよくあるbuild/generatedにコードを作るのではなく直接コードが変更されます。ちょっとこれは怖いかも…

あとは、app/objectbox-models/default.jsonが作成されます。スキーマ情報ですかね。

Insert

Insertはすごく簡単でEntityのインスタンスを作ってBoxというオブジェクトに入れるだけです。

Note note = new Note(0, "text", "comment", new Date());
notesBox.put(note);

idを0にするとAutoIncrementで自動で採番してくれます。Realmには無い機能なのでこれは地味に嬉しいかも。

あと、Realmと違ってTransactionで囲む必要が無いですね。

Update

Updateはidを既存のもにすると更新されますね。他はInsertと同じです。

Delete

削除はidを指定するだけです。他にもEntityをそのまま渡しても削除できます。

notesBox.remove(1);

全件削除も簡単です。

notesBox.removeAll();

Realmの場合は一度Queryで取得してから削除になるので、ここも違いますね。

Query

クエリも単純です。

QueryBuilder<Note> builder = notesBox.query();
List<Note> notes = builder.build().find();

条件指定

QueryBuilder<Note> builder = notesBox.query();
Query<Note> query = builder.equal(Note_.id, 2).build();
List<Note> notes = query.find();

このあたりはRealmとそこまで変わらないですが、カラムの指定がRealmは文字列に対して、ObjectBoxは定数みたいなのが使えます。

test

Gradleのほうで、次のように書いてSyncすると勝手にテストの雛形みたいなのが出来ます。

objectbox {
    generateTests true
}

テスト用のsetupとかはありがたいかもしれないですね。

auto migration

Entityに追加したり、削除したりすると自動でマイグレーションしてくれます。これはかなり強力な気がします。

Entityにフィールドを追加してビルドしたら、勝手にまた色々追加されました。逆に削除したらコンストラクタは変更されたけど、getter/setterは残ってしまいました(バグかな?)

既存レコード側の影響としては、追加されたフィールドは初期値が設定されるみたいで、Stringがnullでintなら0でした。このあたりは制御できるのかな?

さすがに型をStringからintのように変更するのはダメでした。

まとめ

個人的な感想として、軽く触ってみた感じ、だいぶ使いやすい印象でした。

一番ネックになるのがEntityクラスがビルド時に書き換わることかなぁと。ただ、ここを頻繁にいじることがなければ許容範囲かなって気もしますが、どうでしょうかね…

Betaだし、ObjectBrowserも無いので(issueはここ)、まだまだ実際に使うことはないですが期待したいです。

Google Play ConsoleにAPKファイルをアップロードするGradle Pluginを作った

タイトルのとおりです。

ファイルを指定してAPKファイルをPlay Consoleにアップロードするだけです。

あと、Proguardのmappingファイルもついでにアップロードできます。

github.com

たぶん認証のJSONのやり方が分からないかなぁとか思ってます。

今のREADMEはfastlaneのドキュメントにリンクしてるだけです。なので、近いうちにちゃんと書きます。

作った経緯

実はすでにGradle Pluginとしては別のものが存在してました。

github.com

これではダメな理由がいくつかありました。

  • ビルドからアップロードまでが1つのTaskで実行される
    • APKファイルをアップロード前に一度確認したい
  • ストア情報も色々アップロードする
    • そこまでやってくれなくて良い
  • 開発止まってる?PR/issueが処理されてない。

で、ぼくが求めるものが次のような感じです

  • APKファイルを指定してアップロードするだけ
  • ストア情報は特に何もしない
  • Proguard mappingファイルはアップロードしたい

っていうことでこれを満たすように作りました。

おそらく今後、これ以上の機能を追加するつもりはないです。たぶん。

とりあえず

完成したので満足です。