iOSのAdventCalendarということで、iOSエンジニアがkotlinを始めてみてとてもよかったので、普及目的でこの記事を書こうと思います。
それぞれは違う思想で作られているので勉強になります。
RxSwift/Realm/MVVM/swift2.3を採用したプロジェクトを一つ担当して、今はkotlinで全く同じアプリを作ってます。
といってもAndroidでkotlin使って書くのは初めてだったので、もっといいやり方などあればご指摘いただけると大変助かります :)
kotlinにした理由
一番はNull安全なことです。ええ、安全です。あとは以下の理由です。
- var/val でletっぽく書けたりlazyやExtensionでiOSと同じ思想で書ける
- 導入も簡単
- 導入事例も増えてきた
- 構文もシンプル
ちょっとずつ構文が違うので、たまに混乱します。
ここがちょっと違うよiOS -> Android開発
まず違いを感じたのはXcode -> Android Studioです。ボタン多すぎるんですよ。
Xcodeと比べると
- poコマンド的なものがCUIで提供されている
- importしてなくてもコード書いて、import提案してくれる
- ログが見にくい
- 設定ファイル関連のエラーがiOSよりは明確
- Simulatorが多彩すぎ
- projファイルがないので、開くのが面倒
- リソースファイルがめちゃくちゃ多い
- 画像の管理が未だによくわかってない
アーキテクチャ・ライブラリ
MVVMをRxSwiftで作ってみたんですけど、Androidだと近しいものは作れるんですけど、完全再現は難しかったです。結果として以下のようなアーキテクチャ、ライブラリ選定になりました。
新しい技術を使いつつも、後任者に向けてそれぞれが新しい技術+複雑なアーキテクチャになるのは避けたかったのでなるべく同じような実装ができるように心がけました。
デザイン
ここをエンジニアが語るのは恐縮ですが、運用という目線込みで少し言及させてください。開発を始めた頃にデザインをどうするか、考えてたのですが深津さんのこの投稿を見て「そうなのか」と思い色々調べてみました。
https://twitter.com/fladdict/status/804194091135160320- デザイナーがいないプロジェクトだったので、そこまでリソースをかけれない
- Androidはシェアが日本では低いのでいろいろ新機能などを挑戦する場としたい
- 開発にもあまり工数をかけれない
というハードな状況だったので、結果としてiOSとは大きくデザインは異なるものにしました。
結果として提供する少し体験は異なるものの、その際について考察するいい機会となりました。
こちらは一例ですが、同じ画面をAndroid/iOSでそれぞれ作ってみて慣れるとAndroidのほうが早く作れそうだなと感じました。
![スクリーンショット 2016-12-05 12.31.13.png](https://qiita-image-store.s3.amazonaws.com/0/31237/e19e3b04-b42c-2f82-bd96-4d557986c888.png)instant run
前のビルドを維持して、ちょっとしたレイアウトファイルの修正などは即時反映してくれるので便利です
Resourceへのアクセス
iOSでいうところのplistみたいな文字群へのアクセスをするのにContext(アプリケーションの環境情報とかをグローバル(Android OSの全域)で受け渡しするためのインターフェース)が必要になるので、ちょっと面倒。
Activityがこれをメンバ変数として持っているので、これをAPIClientまで渡さないといけない。ここは少し不便だと感じました。いいやりかたがあるんでしょうか?
でも画像やレイアウトファイルの管理の方法自体は非常に便利です。iOSのように画像名を間違えてクラッシュなんてこともないし、動画やその他ファイルへのアクセスも安全で手軽です。
画面の管理
Fragment?Activity?View?どれで画面管理すんの・・・どれがViewControllerでNavigationControllerは??
AndroidエンジニアのためのiOSのUIViewControllerのライフサイクルとAndroidのActivityのライフサイクル比較
iOS と Android で画面表示時のコールバックを比較する
いくつかの参考から**「基本的にはFragmentで書きつつ、モジュールとして再利用性の低い画面などはActivity」**で書いてみました。
iOSでいうところのContainerとして利用する可能性のあるものをFragment、それ以外はすべてActivityで実装しています。これで特に問題は起きませんでした。
ここがちょっと違うよSwift -> kotlin開発
気になったとこと覚えているとこを書きます。
Modelへのマッピング
iOSではhimotoki+RelamでModelへマッピングしてそのまま保存みたいなこともできたんですが、Androidではhimotoki的なものがなく、以下のようにレスポンスをマッピングしました。
// レスポンス
{
"contentName":"zzzzzzz","contentURL":"http://xxxxxx"
}
// 以下のModelにマッピング
// android
class Material{
@SerializedName("contentName")
var name: String? = null
@SerializedName("contentURL")
var url: String? = null
}
@XXXというのはアノテーションといい非常に便利です。用途的にはJava <-> Kotlinの呼び出しで使えたりします。このSerializeNameは便利なんですが、複雑な構造のjsonだと、その構造に対して構造毎classを作るので、ちょっと冗長になります。iOSではHimotokiを使って
// ios
let login = try LoginModel(
nickname: e <| ["data","nickname"]
)
このようにアクセスできたとしてもAndroidでは都度、dataの値を入れるクラスを用いなければいけません。
// android
class LoginModel {
class Data{
@SerializedName("nickname")
var nickName: String? = null
}
@SerializedName("data")
var data: Data? = null
}
当然アクセスも
model.data.nickName
になるので、冗長な感じに・・・何かいいやり方があればご教授いただけると助かります。
Modelの自動生成がAndroidにはいろいろな仕組みがあるので、何も考えずにいったんこういうのを使って生成してから微調整もいいかなと。
Modelの書き方
こんな感じでそれぞれRealmで保存するオブジェクトを書きます。
// android
public open class TeacherModel(
@PrimaryKey
public open var tutorId: String = "",
public open var profileKey:String = ""
): RealmObject(){}
// ios
final class TeacherModel:Object{
dynamic var tutorId:String = ""
dynamic var profileKey:String = ""
}
realm-javaがver0.90.0以前の場合
ただAndroidではRealmObjectに制約が多く、メソッドが実装できない、lateinitは使えないなど諸々の制約があります。そのため、レスポンスと保存オブジェクトてしてのModelを同じにすることはできないため、Entityとしてそもそも別のものとして考えるのがいいかもしれません。
(@zaki50 さんありがとうございます)
0.90.0以降なら使えることを確認しました。またその際にクエリのbetweenで子要素にアクセスするやり方ができなくなったので以下の修正を入れました。
// val res = realm.where(TeacherStore::class.java).between("lessons.startDateTime", startDate.date, endDate.date).findAll()
val res = realm.where(TeacherStore::class.java).greaterThan("lessons.startDateTime", startDate.date).lessThan("lessons.startDateTime", endDate.date).findAll()
Realmへ一対多の子要素へのクエリを使ったアクセス
interfaceは同じですが、relationへのアクセスが少し違いました。
iOSであれば
let datePredicate = NSPredicate(format:" SUBQUERY(schedules, $s, $s.isEmpty == YES AND $s.startDate BETWEEN %@ ).@count > 0", [fromDate, toDate])
let query = NSCompoundPredicate(andPredicateWithSubpredicates: whereArray)
let res = self.realm?.objects(TeacherModel).filter(query)
こんな感じで、subqueryでNSCompoundPredicateを作り、でRelationのプロパティにアクセスして「子要素への条件を指定し、取得する」をしていたんですが、androidでは
val res = realm.where(TeacherStore::class.java).between("lessons.startDateTime", startDate.date, endDate.date).findAll()
こう書けてスッキリ。iOSでも同じ書き方が実はできる気もしますが・・・
UIとの紐付け
// android
class InquiryActivity : AppCompatActivity() {
lateinit var submitButton:Button
override fun onCreate(savedInstanceState: Bundle?) {
val button = findViewById(R.id.button_inquiry_privacy) as Button
}
}
// ios
class InquiryViewController: UIViewController{
@IBOutlet weak var submitButton: UIButton!
}
androidではデフォルトでlayoutファイル上のコンポーネントと変数を紐付けるような機能はないので、紐付けを実装する必要があります。
いまはDataBindingという便利なものもあるので、これを使うのがオススメです。
まとめ
kotlinはほんとに便利だし、AndroidStudioも進化して昔よりズッとandroid開発への抵抗がなくなりました。リソース管理などはAndroidの方が頭がよくて、それにならったR.swiftなんてものも出ているほどです。
どっちも勉強すると大変ですが、両プラットフォームを観れると世界が広がるので面白いです。ぜひこの機会にLet's kotlin!!