「アイデアは考えるな」を読んだ感想

書籍

アイデアは考えるな
面白法人カヤック代表 柳澤大輔
http://amzn.asia/eyJUrs7
  • 会社の本棚にあったので何気なく読んだら、すごく面白くて一気に読み切ってしまった。
  • PM、エンジニア、デザイナー問わず読める本。

イデアは質より量

  • すごいアイデアは誰にでも出せるわけではない、まずはすごくないアイデアをたくさん出そう。
  • すごいアイデアを1つ出すのではなく、すごくないアイデアを10出そう
  • すごくないアイデアを出していけばだんだんとポジティブになり、アイデア出しが面白くなってくる、そこが狙い

なんでも乗っかれ

  • なんでもとにかく目の前に現れたものに悩ますに乗っかろう、自分の判断はあてにならない、自分フィルターを外そう。
  • 乗っかったら面白がれるポイントを探そう(ポジティブに)、視点を変えよう、それが意外な発見につながり、斬新なアイデアに繋がる。
  • ポジティブでなければアイデアはたくさん出せない。
  • イデアとは既存の要素の組み合わせ以外何ものでもない
  • 乗っかるからこそヒントを得てヒットを生み出せる
  • イデアを出すと気には締め切りは絶対に必要、締め切りとは人間の力を目一杯引き出す装置。
  • 情報を集めたほうが格段にアイデアを出しやすい、情報を集めるなら本を読む、ネットで集める、人に会って聞くなど自分にあった方法を見つけろ。

発想法

  • 結果逆算法
    • イデアを出す前に、まず結果をイメージしてどういうアイデアだったらそうなるのかを逆算する、普通の人もぶっとんだアイデアを思いつく
  • マンダラチャート
    • 3*3のマスを使ってアイデアを出していく、アイデア出しの練習にかなり有効
  • イデアの公式
    • 既存のアイデア要素を分割して組み合わせ新しくパターンを作っていく

1人でアイデア出しの練習にはマンダラチャートが良い

ブレスト

  1. とにかくアイデアの量を出す
  2. とにかく相手を否定しない
  3. とにかく相手の意見に乗っかる

ブレストの人数は5〜7人くらいがちょうどいい、それ以上だと発言の量などによる効率が悪い

Android Studio 3.1.1でSyncエラー Data Binding annotation processor version needs to match the Android Gradle Plugin version. You can remove the kapt dependency com.android.databinding:compiler:3.1.0 and Android Gradle Plugin will inject the right version.

タイトルが長すぎるけど気にしない。

Android Studio 3.1.1にアプデしたらSyncエラーが出た。

Data Binding annotation processor version needs to match the Android Gradle Plugin version. You can remove the kapt dependency com.android.databinding:compiler:3.1.0 and Android Gradle Plugin will inject the right version.

日本語訳

データバインディングアノテーションプロセッサのバージョンは、Android Gradle Pluginのバージョンと一致する必要があります。 あなたはkapt依存関係com.android.databinding:compiler:3.1.0を削除することができ、Android Gradle Pluginは適切なバージョンを注入します。

修正方法

メッセージ内容を読むと分かる通り、
以下のapp/build.gradeにあるandroid.databinding:compilerのバージョンをAndroid Gradle PluginのGradleバージョンと合わせれば直る。

Android Gradle PluginのGradleバージョン

    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.1'

3.1.1だね

app/build.gradeにあるandroid.databinding:compiler

kapt 'com.android.databinding:compiler:3.1.0' <- 古い!!

3.1.0かー、ちょっと古いね

こいつを3.1.1にあげよう

kapt 'com.android.databinding:compiler:3.1.1'

これでSyncもビルドも通った。

でも今までkaptもandroid.databinding:compilerもなんとなく使ってきたので、こいつらは一体なんなのか軽く調べた。

kaptとは?

KotlinからJavaのコードを利用可能にするモノらしい。

qiita.com

android.databinding:compilerとは?

以下リンクを見ると、コンパイル時にLayout xmlからBindingのコードを吐き出すものらしい。

sys1yagi.hatenablog.com

つまり?

kapt 'com.android.databinding:compiler とは

DataBindingが定義されたLayout XMLコンパイル時に生成されるコードを、Kotlinで利用可能にするモノのようだ

Androidで処理時間を計測するならSystem#currentTimeMillisよりもSystemClock#uptimeMillis

タイトルまんまの内容。

メソッドの処理時間を計測したかった

Androidで処理時間を計測しようとSystem.currentTimeMillis()を使ってたら、SystemClock.uptimeMillis()のがいいよとアドバイスをもらった。

なぜか?

System.currentTimeMillis()の場合、端末時間をユーザーが変更できたりOSの時刻補正によって現在時間がかわる恐れがあるため

SystemClock.uptimeMillis()は端末ブートしてからの時間を記録してるので、時間に変更が入る心配がない

ただし、SystemClock.uptimeMillis()はディープスリープに入ると止まるため、長い計測を行う場合には注意が必要だ。

参考リンク

Yukiの枝折: 経過時間を求める方法 http://yuki312.blogspot.jp/2011/11/blog-post_30.html

Android KotlinでRealmを使う

環境

資料

Realm公式 Realm: Create reactive mobile apps in a fraction of the time

Realmの導入

プロジェクトのbuild.gradleにrealmを追加

buildscript {
    ext.kotlin_version = '1.2.21'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "io.realm:realm-gradle-plugin:5.0.0" //追加
    }
}

appのbuild.gradleにrealmを追加

ここはrealmを最後の行に書かないとエラーになるはず

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'realm-android' //追加

Realm初期設定

Realm.init

これはアプリ起動中に1回行うだけでいいので、カスタムアプリケーションクラスでやるのがオススメ

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
    }

AndroidManifestにカスタムアプリケーションを登録するのを忘れずに

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="ikemura.com.kotlinrealmmigration"
    >

    <application
+       android:name=".MyApplication" 
        android:allowBackup="true"

Realm使用

インスタンス

いよいよ画面でrealmを使えるようになった

    lateinit var realm: Realm

    private fun initRealm() {
        val realmConfiguration = RealmConfiguration.Builder()
                .deleteRealmIfMigrationNeeded()
                .schemaVersion(0)
                .build()
        realm = Realm.getInstance(realmConfiguration)
    }

Insert

Transaction化しないとIllegalStateExceptionが発生する

        realm.executeTransaction {
            realm.insert(Book(1,"first"))
        }

Read

titleが「first」と一致する値をbookに取得する

        var book = realm.where(Book::class.java)
                .equalTo("title","first")
                .findAll()
        Log.d(TAG,book.toString())

equalTo:カラム名, 値 の比較を行える findAll:クエリ条件を満たすすべてのオブジェクトを同期検索する

Delete

削除もTransaction化しないとIllegalStateExceptionが発生する

        //削除対象を取得
        var book = realm.where(Book::class.java)
                .equalTo("title","first")
                .findAll()
        //削除
        realm.executeTransaction {
            book.deleteAllFromRealm()
        }

nowで作成したAPIをKotlinとRetrofitとRxJavaとJacksonでGET/POSTする

いつもAndroidでRetrofit+RxJavaを使ってAPI通信する処理を書いてるが、今回はKotlinで挑戦してみる。
ついでに、使ったことのないnowもJacksonも触ってみた。

作成するもの

  • nowを使ったnodejs製のAPIモック(express)
  • Androidアプリ(Kotlin/Retrofit/RxJava/Jackson)

環境

API

準備

APIモックをexpressを使ったnodejsで作成し、nowにデプロイしていく。
nowについてはこの記事を参考にした。
以下、作業ログ

// APIモックのプロジェクト準備
$ mkdir try-now && cd try-now
// package.json作成
$ npm init -y
// expressを追加
$ npm i -S express
// apiの処理を書くjsを作成
$ touch index.js

// nowのインストール
$ npm i -G now
// nowのログイン(メールアドレスを入力すると確認メールが届くのでverifyする)
$ now login

実装

index.jsに以下を貼り付ける。
レスポンス変数は適当に作った。

var express = require('express')
var app = express()

app.get('/', function (req, res) {
  const json = {
    status: 1,
    card_status: 10,
    card_type: 'normal',
    card_id: "ca1111",
  }
  res.json(json)
})

app.post('/', function (req, res) {
  const json = {
    status: 2,
    card_status: 20,
    result_code: 1
  }
  res.json(json)
})

app.listen(3000)

ポートは3000を指定してるけど、なくても良い。

これでAPIモックはできあがった。

$ node index.js

としてローカルでも使えるが、今回はnowにデプロイする

デプロイ

nowでデプロイ、超簡単

$ now

https://try-now-kplvmhijbn.now.shというアドレスが表示されたので、これをAPIモックとして利用できる。

このアドレスに対してgetやpostをしていく。

Androidアプリ

使用するライブラリ

app/build.gradleに以下のライブラリ達を追加する

    // Retrofit2
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:converter-jackson:2.3.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'

    // RxJava2
    implementation "io.reactivex.rxjava2:rxjava:2.1.9"

    // RxAndroid
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'

    // Jackson
    implementation 'com.fasterxml.jackson.core:jackson-core:2.9.4'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.4'
    implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.4'
    implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.4"

ソース:https://github.com/banbara23/try-now

実装

Retrofitのインタフェースを作成。

戻り値はRxjavaのObservableにする。

interface ApiService {
    @POST("/")
    fun post(): Observable<PostResponse>

    @GET("/")
    fun get(): Observable<GetResponse>
}

APIクライアントを作成していく。

今回、baseUrlにはnowで作成したurlを設定する。

class ApiClient {
    var apiService: ApiService

    init {
        val retrofit: Retrofit = Retrofit.Builder()
                .baseUrl("https://try-now-kplvmhijbn.now.sh")
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(JacksonConverterFactory.create(ObjectMapper()))
                .build()
        apiService = retrofit.create(ApiService::class.java)
    }

    /**
     * GETリクエスト
     */
    fun get(): Observable<GetResponse> =
            apiService.get()

    /**
     * POSTリクエスト
     */
    fun post(): Observable<PostResponse> =
            apiService.post()
}

APIクライアント呼び出しは以下

ラムダ初めて触った、すごい

  // get
        apiClient.post()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(
                        { res -> Log.d(TAG, res.toString()) },
                        { error -> Log.e(TAG, "{$error.message}") },
                        { Log.d(TAG, "post completed") }
                )

  // post
        apiClient.post()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(
                        { res -> Log.d(TAG, res.toString()) },
                        { error -> Log.e(TAG, "{$error.message}") },
                        { Log.d(TAG, "post completed") }
                )

モデルについては、 Response(共通レスポンス)を継承した各モデルが存在するイメージで作成した。

/**
 * 共通レスポンス
 */
open class Response(

        @field:JsonProperty("status") val status: Int = 0,
        @field:JsonProperty("card_status") val cardStatus: Int = 0
)
/**
 * GETレスポンスのモデル
 */
data class GetResponse(
        @field:JsonProperty("card_type") val cardType: String = "",
        @field:JsonProperty("card_id") val cardId: String = ""
) : Response() {
    override fun toString(): String =
            "GetResponse(status='$status' cardStatus='$cardStatus' cardType='$cardType', cardId='$cardId')"
}
/**
 * POSTレスポンスのモデル
 */
data class PostResponse(
        @field:JsonProperty("card_type") val cardType: String = "",
        @field:JsonProperty("card_id") val cardId: String = "",
        @field:JsonProperty("result_code") val resultCode: Int = 0
) : Response() {
    override fun toString(): String =
            "PostResponse(status='$status' cardStatus='$cardStatus' resultCode='$resultCode')"
}

この継承してるあたり、もうちょいうまくできないだろうか。

ビルドして実際に動作させると、Logcatに以下のログが表示されていればGET/POSTが成功している

getのログ
D/AppCompatActivity: GetResponse(status='1' cardStatus='10' cardType='normal', cardId='ca1111')
D/AppCompatActivity: get completed

postのログ
D/AppCompatActivity: PostResponse(status='2' cardStatus='20' resultCode='1')
D/AppCompatActivity: post completed

ソース:https://github.com/banbara23/Kotlin-Jackson-Sample

ハマったところ

Jacksonが@JsonPropertyを認識してくれない

APIレスポンスをJacksonでシリアライズする際、共通レスポンスモデルであるResponse.ktがKotlin用のアノテーションでないとエラーになる。

Jacksonが継承元の変数を@JsonPropertyだと認識してくれなかった。
継承先は@JsonPropertyでも大丈夫なのに…

NG : @JsonProperty
OK : @field:JsonProperty

実際のコード

/**
 * 共通レスポンス
 */
open class Response(
        // NG
        //@JsonProperty("status") val status: Int = 0,
        //@JsonProperty("card_status") val cardStatus: Int = 0

        // OK
        @field:JsonProperty("status") val status: Int = 0,
        @field:JsonProperty("card_status") val cardStatus: Int = 0
)

同様の問題が起きてないか調べたところ、以下のリンクに辿り着き、kotlinアノテーションを使うことで解決できた。 - https://github.com/FasterXML/jackson-module-kotlin/issues/56#issuecomment-321932309 - https://github.com/FasterXML/jackson-module-kotlin/issues/98#issuecomment-352016501

感想

nowが楽すぎてすごい

kotlinだとJavaよりコード量少なくて楽で良い

IllegalArgumentException: *** is not part of the schema for this Realm

環境

現象

既存のJavaAndroid StudioプロジェクトにKotlinを導入してビルドしたところ、起動時以下のエラーが必ず発生してお手上げとなった。

java.lang.RuntimeException: Unable to resume activity {my.package.MainActivity}: java.lang.IllegalArgumentException: MyMessageData is not part of the schema for this Realm
      at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3429)
      at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3469)
      at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2732)
      at android.app.ActivityThread.-wrap12(ActivityThread.java)
      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
      at android.os.Handler.dispatchMessage(Handler.java:102)
      at android.os.Looper.loop(Looper.java:154)
      at android.app.ActivityThread.main(ActivityThread.java:6119)
      at java.lang.reflect.Method.invoke(Native Method)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
   Caused by: java.lang.IllegalArgumentException: MessageData is not part of the schema for this Realm
      at io.realm.internal.modules.CompositeMediator.getMediator(CompositeMediator.java:169)
      at io.realm.internal.modules.CompositeMediator.getTableName(CompositeMediator.java:87)
      at io.realm.RealmSchema.getTable(RealmSchema.java:218)
      at io.realm.RealmSchema.getSchemaForClass(RealmSchema.java:239)
      at io.realm.RealmQuery.<init>(RealmQuery.java:122)
      at io.realm.RealmQuery.createQuery(RealmQuery.java:76)
      at io.realm.Realm.where(Realm.java:1342)

MyMessageData is not part of the schema for this Realm
日本語訳:MyMessageDataは、このレルムのスキーマの一部ではありません

おかしいな、Kotlin追加する前までビルド通ってたんだけどなー

解決方法

なんと、app/build.gradleの宣言順を入れ替えるだけで解決した。

Before

apply plugin: 'com.android.application'
apply plugin: 'realm-android'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'

android {
...

After

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android' // <= こいつを一番最後にする

android {
...

apply plugin: 'realm-android'よ、お前は一番最後だったのか…
これだけでビルドが通った。

エラーでググるとStackOverFlowにも回答がすぐに見つかったため、割と同様にハマっている人が多いのかもしれない。

参考リンク

KotlinでDatabaseException: mypackage.Item does not define a no-argument constructor.

状況

Android Studio: 3.0.1
Kotlin: 1.2.10
firebase-database: 11.8.0

現象

Kotlinの勉強がてらFirebaseのRealTime Databaseでデータを取得しようとしたらこんなエラーが出た。

com.google.firebase.database.DatabaseException: Class my.com.package.Item does not define a no-argument constructor. If you are using ProGuard, make sure these constructors are not stripped.
    at com.google.android.gms.internal.zzelx.zze(Unknown Source)
    at com.google.android.gms.internal.zzelw.zzb(Unknown Source)
    at com.google.android.gms.internal.zzelw.zza(Unknown Source)
    at com.google.android.gms.internal.zzelw.zza(Unknown Source)
    at com.google.android.gms.internal.zzelw.zza(Unknown Source)
    at com.google.firebase.database.DataSnapshot.getValue(Unknown Source)
    at my.package.MyListFragment$fetchData$1.onDataChange(MyListFragment.kt:83)
    at com.google.android.gms.internal.zzegf.zza(Unknown Source)
    at com.google.android.gms.internal.zzeia.zzbyc(Unknown Source)
    at com.google.android.gms.internal.zzeig.run(Unknown Source)
    at android.os.Handler.handleCallback(Handler.java:751)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6119)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

※パッケージやクラス名は変更している

firebaseからの取得コードはこれ

    /**
     * 一覧に表示するデータを取得
     */
    private fun fetchData() {
        // Write a message to the database
        val database = FirebaseDatabase.getInstance()
        val myRef = database.getReference(mPath)

        // Read from the database
        myRef.addValueEventListener(object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                // This method is called once with the initial value and again
                // whenever data at this location is updated.
                val items: List<Item>? = dataSnapshot.getValue(object : GenericTypeIndicator<ArrayList<Item>>() {})
                Log.d(TAG, "Value is: " + items)
                recyclerView!!.adapter = MyRecyclerViewAdapter(items!!, mListener)
            }

            override fun onCancelled(error: DatabaseError) {
                // Failed to read value
                Log.w(TAG, "Failed to read value.", error.toException())
            }
        })
    }

エラーメッセージのみ抜き出すと
Class my.com.package.Item does not define a no-argument constructor. If you are using ProGuard, make sure these constructors are not stripped.

日本語に訳すと
クラスmy.com.package.Itemは、引数のないコンストラクタを定義しません。 ProGuardを使用している場合は、これらのコンストラクタが削除されていないことを確認してください。

解決方法

引数のないコンストラクタ…
もしかしてモデルとして作ったItemクラスがおかしいのか?
と思い見てみると、ああ、Boolean型のisVideoに初期値を定義していなかった。

// 修正前
data class Item(
        val caption: String = "",
        val code: String = "",
        val data: String = "",
        val isVideo: Boolean,  // ←こいつの初期値がないのが原因
        val thumbnail: String = ""
)

とりあえずfalseでも突っ込んでおく

// 修正前
data class Item(
        val caption: String = "",
        val code: String = "",
        val data: String = "",
        val isVideo: Boolean = false,  //初期値falseを追加
        val thumbnail: String = ""
)

ビルドして動作させると...動いた!

モデルのコンストラクタに初期値を忘れるとこうなるんだな、勉強になった。