へいへいブログ

📝📝📝📝📝📝

Room Persistence Libraryを訳してみた(※2017年5月末時点)

f:id:shaunkawano:20170530122939p:plain

自分へのメモ程度にRoom Persistence Library | Android Developersを日本語訳しましたので、この記事ではその内容を記載いたします。(※注意: この翻訳記事は2017年5月末時点での上記の公式ドキュメントの日本語訳です。)

雑に訳しています。タイポ、細かい表現の差異、その他誤りなどありましたら(あくまでも個人用メモとして殴り書きしておりますので、ご了承下さい。)、ご連絡頂けますと幸いです。なるべく早く修正致します。m(__)m

※2017年5月30日修正: 細かいタイポや文言の修正と、本記事の最下にCA.apk #3 - Google I/O 2017 報告会(2017/05/29開催)でのRoomに関する発表スライドを埋め込みました。

Room Persistence Library

コアフレームワークSQLコンテンツをサポートしている。API自体はパワフルだが、低レイヤーなものであるため使いこなすまでになかなかの時間と努力を必要とする。さらに、

  1. 生のSQLクエリにはコンパイルタイム検証・解析が存在しない。データグラフの変更に伴い、変更による影響を受けたSQLクエリを手動で更新する必要がある。時間がかかると同時に、バグの原因となりやすい。
  2. SQLクエリをJavaのデータオブジェクトに変換するために、たくさんの決まり文句のコードを利用する必要がある

RoomはSQLiteの抽象的レイヤーの機能を提供しつつ、上記1、2の問題を解決するためのライブラリ。

Roomには3つの主要なコンポーネントがある

1. Database

Database Holderを生成するために利用。@Databaseアノテーションを利用してEntityの一覧を定義し、Databaseクラスの内容にはDAOの一覧を記載。同様に、Databaseオブジェクトは根本的な接続のための中枢アクセスポイントである。

2. Entity

データベースの列を持つクラスを表す。それぞれのEntityに対してデータベーステーブルが生成され要素を保存する。Databaseクラスのentities配列でEntityを参照する必要がある。@Ignoreをフィールドに付与しない限り、Entityクラス内のすべてのフィールドはデータベース内に永続化される。

Note: 
Entityクラスには、(DAOクラスがフィールドに直接アクセスすることができる場合のみ)
空のコンストラクタ、もしくは最低1つのフィールドパラメーターを受け取るコンストラクタを定義する必要がある。

3. DAO

DAO(Data Access Object)のクラスもしくはインタフェースを表す。DAOとはRoomにおける主要のコンポーネントであり、データベースにアクセスするためのメソッドを定義する責務を持つ。@Databaseアノテーションが付与されているクラスは@Daoアノテーションが付与されているクラスを返す引数が存在しないabstractメソッドを持つ必要がある。コード生成を行うコンパイル時にRoomはこのクラスの実装コードを生成する。

Important:
クエリービルダーや直接クエリーを文字列として記載するのではなくDAOクラスからデータベースにアクセスをすることで、
データベース構成を別の要素に切り分けることができる。
さらにいえば、テストを行う際にはDAOによってデータベースアクセス処理を簡単にモック化することができる。
Note: 
Databseクラスの生成にはコストがかかり、
また複数のインスタンスにアクセスする必要があるケースはほとんどないため、
データベースオブジェクトの生成時にはSingletonデザインパターンに準拠するべきである。

Entities

クラスに@Entityアノテーションが付与されていて、かつそのクラスが@Databaseアノテーションが付与されているクラスのentitiesプロパティ内で参照されている場合、RoomはこのEntityクラスのデータベーステーブルを生成する。

デフォルトでは、RoomはEntityクラス内に定義されているフィールドすべてに対応するカラムを1つ1つ生成する。 もしEntityクラス内に永続化したくないフィールドが存在すれば、@Ignoreアノテーションを付与することで永続化を回避することができる。フィールドを永続化するために、Roomはフィールドにアクセスできる必要がある。フィールド自体にpublic修飾子を付与するか、セッターとゲッターを提供することができる。セッターとゲッターを利用する際には、RoomではJava Beans機構にそっている必要があることを忘れてはいけない。

Primary Key

それぞれのEntityは最低1つのフィールドをPrimary keyとして定義する必要がある。フィールドが1つしか定義されていない場合であっても、@PrimaryKeyアノテーションを付与する必要がある。同様に、RoomにEntityに対して自動ID付与をしてほしい場合、@PrimaryKeyautoGenerateプロパティをセットすることができる。もしEntityが複数のPrimaryKeyを持っている場合には@Entity(primaryKeys = {"", ""}を利用することができる。 デフォルトでは、Roomはクラス名をデータベースのテーブル名として利用する。もし別名をテーブル名として利用したい場合には、@Entity(tableName="")プロパティを利用できる。

注意:
SQLiteにおけるテーブル名はケースセンシティブである。

tableNameプロパティ同様に、デフォルトではRoomはフィールド名をデータベース内のカラム名として利用する。 もし別名をカラム名として利用したい場合には@ColumnInfo(name = "")を利用できる。

Indexとユニーク性

データアクセスの方法によっては、特定のフィールドをインデックス化してクエリを高速化したい場合がある。 Entityに対してインデックスを追加するためには

@Entity(indices = {@Index("name"), @Index("last_name", "address")})

を利用できる。

ときにはデータベース内の特定のフィールド、またはフィールドの集合がユニークである必要がある。 これらの独自性を遵守するには

@Entity(indices= { @Index(value = {""}, unique = true }

を利用できる。

リレーショナルについて

SQLiteはリレーショナルデータベースであるため、オブジェクト間の関係性を細かく指定することができる。 ほとんどのORMライブラリはEntityオブジェクト同士の参照をサポートしているが、Roomは明示的にこれを禁止している。

補足

Entity間のオブジェクト参照の禁止

データベースから対応するオブジェクトモデルへの関係性のマッピングはよく知られている実践方法であり、特に遅延読み込みされるフィールドアクセスに対するパフォーマンスが良いサーバーサイドにおいてはよく機能する。 しかし、クライアントサイドにおいては遅延読み込みはうまくいかない。なぜなら読み込み処理がUIスレッドで発生しやすく、ディスク上の情報のクエリをUIスレッドで行うことは重大なパフォーマンス問題を引き起こす可能性があるため。UIスレッドにはアクティビティのレイアウト更新のための計算と描画におおよそ16msの時間しか用意されていないため、たとえクエリにかかる時間がたったの5msのみであったとしても、アプリケーションがフレームを描画するための時間は足りなくなってしまい、ユーザーが認識できるジャンクを発生させてしまう。さらにもっと悪い場合、複数処理が同時並行で走っている際や端末自体がディスクに負荷のかかる処理を行っているとクエリにかかる時間が単純に16msを超えてしまうかもしれない。しかし遅延読み込み処理を行わない場合だと、アプリケーションは必要以上のデータを読み込み、メモリー消費問題を起こしてしまう。

ORMライブラリはこれらの議論を開発者に託しているため、開発者は自分たちが最善だと思う方法でORMをアプリ開発に利用している。不運なことに、一般的には開発者は遅延読み込みするモデルをアプリケーションとUI間の両方で利用してしまうケースに陥ってしまう。

例えば、Authorオブジェクトを持つBookオブジェクトを複数読み込むようなUIを想定する。初期段階では遅延読み込みを利用したクエリ設計によってBookオブジェクト内にgetAuthor()メソッドを用意し、Authorオブジェクトを返却するようにしたとする。この設計により初回のgetAuthor()メソッド実行時にはデータベースへのクエリ処理が走る実装となる。月日が経ち、仕様変更などによりUIに今までのものに加えて筆者の名前を表示する必要が出てきた。getAuthor().getName()メソッドを追加することでとても容易にUI実装をすることができた:

authorNameTextView.setText(user.getAuthor().getName());

このような何事もないようなシンプルな変更によって、気づかぬうちにAuthorテーブルへのクエリ処理がメインスレッドで行われるようになってしまう。

これらの理由から、RoomはEntity間のオブジェクト参照を禁止し、代わりに開発者にアプリが本当に必要なデータのみを明示的にリクエストしなければいけないような設計になっている。

直接的なリレーションは行えないが、RoomはEntity間におけるForeignKey制約の定義を許容している。

@Entity(foreignKeys = @ForeignKey(entity = User.class,
                                  parentColumns = "id",
                                  childColumns = "user_id"))

ForeignKeyはとても強力で、参照されているEntityが更新されてたタイミングで何が起こるかを指定することができる。たとえば、UserオブジェクトがDBから削除されたら、SQLiteにユーザーのBook情報すべてを削除するように指定することができる。

詳細 https://sqlite.org/lang_conflict.html

オブジェクトのネスト

時々、オブジェクトが複数のフィールドを保持している場合であっても、そのEntityやPOJOクラスのオブジェクトをひとまとまりのデータベースロジックとして表現したい場合がある。それらの場合には、@Embeddedアノテーションを利用することで、特定のEntityクラス内に別の@Entityクラスを埋め込むことができる。

たとえば、UserクラスがAdressクラスタイプのフィールド(Addressクラスはstreet, city, state, post Codeのような複数のフィールドを保持したクラス)を内包することができる。

class Address {
    public String street;
    public String state;
    public String city;

    @ColumnInfo(name = "post_code")
    public int postCode;
}

@Entity
class User {
    @PrimaryKey
    public int id;

    public String firstName;

    @Embedded
    public Address address;
}

データベーステーブルはUserというテーブル名で、カラムはid, firstName, street, state, city, post_codeとなる

Note: Embeddedフィールドは同様に他のEmbeddedフィールドを内包することができる。

もしEntityが複数の同じタイプのEmbeddedフィールドを保持していた場合は、それぞれのカラムの別名性を保つためにprefixを付与することができる。

DAO(Data Access Objects)

Roomの主要なコンポーネントはDaoクラス。DAOはデータベースアクセス処理を簡潔に抽象化する。

Insert

@Insert public void insertBothUsers(User user1, User user2);
@Insert public void insertUsersAndFriends(User user, List<Friend> friends);

もし@Insertメソッドの受け取る引数の数が1つだった場合、メソッドは新しいrowIdを表すlong値を戻り値として返すことができる。もし引数が配列もしくはコレクション型であれば、戻り値にはlong[]またはListを指定できる。

https://www.sqlite.org/rowidtable.html

Update

@Update public void updateUsers(User… Users);

ほとんどの場合必要ないかもしれないが、更新がかけられた列の数を表すint型を戻り値として返すことができる。

Delete

@Delete public void deleteUsers(User… Users);

ほとんどの場合必要ないかもしれないが、削除された列の数を表すint型を戻り値として返すことができる。

@Queryアノテーションを使ったメソッド

データベース上の読み込み・書き込み処理を行うためのアノテーション。それぞれの@Queryメソッドはコンパイル時に検証され、問題があればコンパイルエラーが発生し通知が行われる。同様に、Roomはクエリメソッドの戻り値も検証し、もし戻り値のオブジェクト内のフィールド名が対応するクエリ結果のカラム名とマッチしない場合は、以下のいずれかの方法でアラートする。

  • いくつかのフィールド名だけマッチしていない場合は警告
  • 全てのフィールド名がマッチしていない場合はエラー

もしクエリがシンタックスエラーを含んでいる場合やテーブルがデータベース内に定義されていない場合も同様にコンパイル時にエラーとして通知する。

Queryにパラメーターを渡す

データベースアクセスをする際、大抵の場合はパラメータをクエリに渡してフィルター処理を行う。 このような場合にはRoomアノテーション内でメソッドパラメータを利用する。

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge")
    public User[] loadAllUsersOlderThan(int minAge);
}

クエリがコンパイル時に処理される際、Roomは:minAgeバインド引数名がメソッド引数名と対応しているかを確認する。パラメータ名を利用した整合性チェックをRoomは行う。もしミスマッチが存在する場合にはエラーが発生する。

同様に複数の引数を渡したり1つの引数を複数回参照することができる。

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
    public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

    @Query("SELECT * FROM user WHERE first_name LIKE :search "
           + "OR last_name LIKE :search")
    public List<User> findUserWithName(String search);
}

カラムの一部分のみを返す

大抵の場合、Entity内のいくつかのフィールドのみが必要な場合がある。たとえばUIに表示するのはユーザーのファーストネームとラストネームのみで、ユーザーに関する細かい情報すべてを読み取る必要がない場合など。そのような場合には必要なカラムからのみ情報を読み込むことで、変数へのリソース割当を少なくし、より早くデータベースクエリーを完了することにもつながる。

Roomではクエリで選択されるカラムの一覧が戻り値のオブジェクトとマッピングすることができれば、どんなJavaオブジェクトでも戻り値として指定することができる。

public class NameTuple {
    @ColumnInfo(name="first_name")
    public String firstName;

    @ColumnInfo(name="last_name")
    public String lastName;
}
@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user")
    public List<NameTuple> loadFullName();
}

もしカラム数が多すぎる場合や指定されたカラムが戻り値に指定されたクラスと対応できない、存在しない場合には警告が表示される。

NOTE: そのような場合には@Embeddedアノテーションを利用できる。

引数としてCollectionを利用する

時にはランタイム時にしか正確な引数の数がわからないような場合がある。たとえばいくつかの地区に関する情報を取得したい場合、などだ。Roomは引数がCollectionであるかどうかを理解できるため、Collectionの場合には自動的にクエリが拡張されランタイム時に正確な数の引数がメソッドに与えられる。

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    public List<NameTuple> loadUsersFromRegions(List<String> regions);
}

Observable Queries

データが更新されたタイミングでアプリケーションのUIも自動で更新したい場合がある。そのためにはRoomのDaoのメソッドの戻り値をLiveData型にする。Roomはデータベース更新と同時にLiveDataの更新に必要なコードすべてを生成する。

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}
(!!ここはどう訳すのか、まだ理解が曖昧です。。!!)
バージョン1.0では、Roomはクエリでアクセスされたテーブルの一覧を利用してLiveDataオブジェクトの更新が必要かどうかを判断する?

RxJava

同様に、RoomではRxJava2のPublisherとFlowableオブジェクトをクエリの戻り値として指定できる。

android.arch.persistence.room:rxjava2

Dependencyを追加することで戻り値にPublisherもしくはFlowableを指定できる。

Cursorへの直接アクセス

もしアプリケーションのロジック上、帰ってきたデータベースのRowに直接アクセスする必要がある場合には、Cursorオブジェクトを戻り値として指定することができる。

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    public Cursor loadRawUsersOlderThan(int minAge);
}
注意:
Rowが実際に存在するかどうか、またはどのような値がRowに存在するのかの保証がないため、Cursor API利用は推奨されていない。このような実装がすでにアプリ内に存在し、リファクタリングが容易にいかない場合にのみ利用するべきである。

複数テーブルクエリ

クエリには、結果を計算するために複数テーブルにアクセスする必要があるものがある。Roomはどのようなクエリ文であっても記述することを許容しているため、join文も記載することができる。RxJava2のFlowable, PublisherやLiveDataを戻り値として指定している場合には、クエリで参照されているすべてのテーブルをエラー検知のために監視する。

TypeConverter

(筆者メモ: MoshiのTypeAdapterのような機能。) @TyperConverterアノテーションをメソッドに付与し、@TypeConvertersアノテーションをDBクラス、Daoのメソッド、Entityクラス、など様々な場所に付与することで利用できる。

@Database(entities = {User.java}, version = 1)
@TypeConverters({Converter.class})
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

データベースマイグレーション

Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
        .addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
                + "`name` TEXT, PRIMARY KEY(`id`))");
    }
};

static final Migration MIGRATION_2_3 = new Migration(2, 3) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE Book "
                + " ADD COLUMN pub_year INTEGER");
    }
};

Migrationクラスを作成する。ランタイム時にRoomはMigrationクラスのmigrate()メソッドを順番に実行する。 もし必要なマイグレーション処理がない場合にはRoomはDBを再構築するので、データは消える。

Migration完了後にRoomはスキーマを確認しMigrationが正しく行われたかどうかを確認する。もし失敗した場合にはExceptionが発生する。

Testing migrations

データベーススキーマをエクスポートすることでマイグレーション処理のテストを事前に行うことができる。

Exporting Schemas

Roomはコンパイル時にスキーマ情報をJSONファイルに吐き出すことができる。スキーマをエクスポートするには build.gradleに以下を追加する:

android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation":
                             "$projectDir/schemas".toString()]
            }
        }
    }
}

エクスポートされたJSONファイルはバージョン管理しておくことで今後マイグレーションテストの際、Roomから古いスキーマのデータベース生成を行うことができる。

android.arch.persistence.room:testing 

dependencyを追加する。 スキーマ管理を行っているパスをasset folderとして指定する。

android {
  … 
  sourceSets {
    androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
  }
}

testingパッケージにはMigrationTestHelperクラスが存在し、このrunMigrationsAndValidate()メソッドを実行することでhelperクラスが自動でスキーマ変更を検証する。データの変更に関しては自分で検証する必要がある。


最後に

上記が自分が行ったRoomの公式ドキュメントの和訳の全文となります。メモ程度に書いたものですので、あくまで参考程度に気になった箇所をつまむ程度に読んでいただけると幸いです。

また、2017年5/29日にCA.apkにてRoomに関する発表を行いましたので、Roomに関する情報をお求めの方はこちらも合わせて御覧ください。

Google I/O 2017 - 現地入りするまで&持っていった荷物まとめ

Google I/O 2017に(初)現地参加してきました。この記事では、初めてGoogle I/Oに参加するにあたり個人的に行ったこと、日本出発から現地入りするまでのまとめ、そしてアメリカに持参した物の一覧を記載しています。今後、渡米する方やGoogle I/Oに参加する方にとって少しでも参考になれば幸いです。

初めてGoogle I/Oに参加するにあたり個人的に行ったこと

参考になりそうな記事を読む

こちらの記事は軽く目を通しました。

参加者に持参した方がいいものを聞く

以前にGoogle I/Oに参加したことのある方々に持参した方がいいものなどを質問したりしました。「夜は冷えるからライトダウンジャケットは必須」、「日焼け止め必須」などのアドバイスをいただきました。

今後、初めて参加される方は実際に参加したことある方々に必要なものなどを質問することをおすすめします。

ESTA申請

上記記事に詳しく記載されています。出発の数日前に( ゚д゚)ハッ!と思い出して慌てて申請しました。申請後、数分で審査通りました。出国日が近づくと荷物の準備などに追われて忘れてしまうことがあるので、申請が必要な方は渡米が決まったタイミングで早めに行うことをおすすめします。

時差について

日本とサンフランシスコの時差は、

です。アメリカよりも日本のほうが時間は進んでいるため、例えば日本を5/5日の夕方に出発したとすれば、現地に到着すると再び5/5日の早朝です。

日本出発から現地入りするまで

飛行機の出発時刻は17:10で、現地時刻の10:35到着予定でした。日本時間の17時は現地時刻の25時なので、フライト後なるべく早く機内で睡眠を取り、フライトの途中、後半頃に目覚めることができれば時差ボケを最小限にできると考えました。離陸後すぐに靴からサンダルに履き替えアイマスクと喉を潤すマスクを着用し、機内食が出て少し経った消灯のタイミングで速攻で眠るよう心がけました。(思ったほど眠りにつくことはできませんでしたが、)なんとか現地到着後に夜まで眠らず活動したおかげでそこまで深刻な時差ボケには至りませんでした。

アメリカに持参した物一覧

  • Eチケット(必須)
  • パスポート(必須)
  • アメリカドル($500程度)(Google I/Oとお土産はほとんどクレジットカード支払いだったのでお金は余りました。)(クレジットカードのみ利用可能の場合が多かったです。)
  • Google I/OチケットQRコード、パスポート、航空券、ESTAの印刷(上記参考にした記事の勧めを読み持参。必須ではないですが安心感はありました。)

生活品

  • 歯ブラシ(ホテルにはありませんでした。)
  • 日焼け止め(Google I/O参加者には配布されます。現地でも買えます。)
  • 帽子(ほとんど利用しませんでした。)(現地でも買えます。)
  • タオル
  • 着替え
  • ライトダウン(寒い日の夕方・夜かなり役立ちました。)(現地ユニクロでも買えると思います。)
  • 部屋着
  • サンダル
  • 傘(雨はほとんど降らなかったため利用しませんでした。)
  • コンタクトレンズ・洗浄液
  • ひげそり
  • 爪切り

医薬品

  • ベンザブロック(調子が悪くなった際利用しました。持参するだけでも安心感はあります。)
  • メンソレータム

電化製品

機内用品

  • 潤いマスク
  • 機内用枕(成田空港内にある無印で購入)

まとめ

ざっとですが、初Google I/Oにあたり個人的に行ったこと、日本出発から現地入りするまでのまとめと、アメリカに持参した物の一覧を記載しました。今後Google I/Oに参加する方だけではなく、アメリカに出国される方も、何かご質問ありましたらお気軽にご連絡下さい。

以上です。

f:id:shaunkawano:20170528152304j:plain

#potatotips #38 にブログ枠で参加してきた

potatotips #38 (iOS/Android開発Tips共有会) - connpassにブログ枠で参加しました。 このブログ記事では、Androidの発表に関するまとめを記載致します。

会場@Origami, Inc.

会場は、南青山にあるOrigami, Inc.さんのオフィスでした。Origami - スマートなお支払いを。

f:id:shaunkawano:20170322190444j:plain

Android発表枠のまとめ

以下、Android発表枠のまとめです。

Keisuke Kobayashi Practical Implementation of Up Navigation

アプリの詳細面上でUpボタン(左上の「戻る」)を押下した際の動きの制御についての発表でした。シンプルなアプリであれば純粋にfinish()を呼べば良いかもしれませんが、例えば…

  • 検索結果から詳細面に遷移した際
  • DeepLinkから詳細面を起動した際

上記の場合、単にアプリを閉じるのではなく詳細面の上位面に遷移してほしいことがあります。その場合の解決策として、遷移元のActivityの識別子を保持しSINGLE_TOPフラグを用いてActivityを再生成しない方法が紹介されています。詳しくは下記記事もどうぞ。

NavUtils#navigateUpFromSameTask)なども登場します。

NavUtils自体初耳だったことと、これから業務でUpボタン周りの制御を行う予定なので、ドキュメント読んで考えながらやっていこうと思います。

r21nomi Let’s make Photo Frame with Android Things

Raspberry PiやEdison向けのAndroidアプリを作ることができるAndroid Thingsを使って、PinterestでPinした画像を表示するフォトフレームを作ったことについての発表でした。Pinterest APIを利用するためのアクセストークンをAndroid Things側から取得しようとするとAndroid ThingsのSDKの不具合によってintentを飛ばしたりWebViewを起動してトークンを取得する際にクラッシュしてしまうとのこと。解決策としてスマホ側の独自のアプリでPinterestOAuth認証を行いそのトークンをFirebase Realtime Databaseを用いてAndroid Things側に同期するという方法を取ったということです。

Android Thingsまだ触っていないので興味があります。何か作るか。(SDK怖い、アニメーション周りがすこし苦手らしい) ソースコードr21nomi/pin-board: pin-board is Pinterest client for Android.にあるそうです。

androhi ConstraintLayout再入門

ConstraintLayout再入門についての発表でした。ConstraintLayout(RelativeLayoutのすごいやつ)は現在バージョン1.0.2までリリースされていて、安定してきていると感じています。新しいLayoutEditorでViewをゴニョゴニョいじり制約をつけることができます。アスペクト比でViewの大きさを決めれるようになったり、View同士を鎖でつないだような実装を行えるとのことです。また、Android Studio2.3では新しいテンプレートが追加されているようです。

ConstraintLayoutも同じくまだあまり触ったことがないので、まずはサンプルで遊んでみようと思います。

pside MediaSessionの話

MediaSessionの話 - Google スライド

メディア再生アプリを作る場合に独自のゴニョゴニョ実装をするのではなくMediaSessionを利用しようという話でした。

MediaSessionCompatPlaybackStateCompatを利用することで一時停止中、再生中、早送り可能、巻戻り可能などの状態(State)を制御できるとのこと。 また、Googleのサンプルコードがかなり参考になるそうです。制御するStateを指定する際にBuilderで設定するそうなのですが、setActionsのメソッドに適した値を指定をすることで正しいCallbackを受け取れるそうです(逆にちゃんと指定しないとハマるらとのこと。)

メディア再生アプリ作ったことがなかったので真新しい情報ばかりで学びが多かったです。

分かってしまえばどうってことはないが、ここまで辿り着くのに相当かかった

ということはメディア再生にかぎらずAndroidあるあるなのかなと思うので、精進します!(`・ω・´)ゞ

発表内容はI/O 2016: Best Practices in Media Playback on Android // Speaker Deckをベースにしているとのことです。

おまけ

ご飯もオフィスもとてもオシャレでしたლ(´ڡ`ლ)

f:id:shaunkawano:20170322205811j:plain

はてなブログ作ってみた

今まで こっちのブログに日本語や英語のブログをぼてぼて書いていたけど、はてなブログは使ったことがなかったので一度使ってみようと思いブログ作ってみた。

はてなブログには日本語の記事を。http://shaunkawano.comのほうには英語の記事を引き続き書いていく予定です。 続くかどうかは全くわからないけれど。