一番最新のやつはこっち
rei19.hatenablog.com
前回はこちら
rei19.hatenablog.com
- 前のプロジェクトで開発していたアプリをリリースしてからはAndroidから離れていたのですが、会社変えてまた触ることになったので思い出す意味でも再考します。
- 前回は出来るだけ純正のAPIだけで考えたのですが、今回はライブラリも使いながらもう少し手を入れてみたり。
今一度ポイント
- 基本的にModelは死なない + 通知で変更を伝える役割を持つ
- ViewやVCでAPIを呼ぶのはユーザーの操作に影響されやすいからやめれ
変えたところ
- Android Best Practiceを参考にパッケージ構成を整理
github.com
github.com
- 今、Kotlin楽しい感じなので、Kotlinで書きなおしてます。考え方は変わらないのでJavaに置き換えて読むとよいかと。
パッケージ構成
パッケージ名 |
役割 |
entities |
各ドメインで扱うデータクラスを配置 |
events |
EventBusを通して発火するイベントクラスを配置 |
fragments |
Fragmentを配置 |
managers |
多様なデータを扱うコントローラを配置 |
models |
各ドメインモデルを配置 |
network |
外部との通信が発生するモジュールを配置 |
utils |
ユーティリティー群 |
views |
Viewに関連するモジュールを配置 |
コード
- managers/ModelLocator
- こちらは先程のandroid-mvc-sampleをKotlinで焼き直し
package me.rei_m.kotlinmvcsample.managers
import java.util.HashMap
各Modelのインスタンスを管理する
public class ModelLocator private constructor() {
companion object {
private val showcase = HashMap<Tag, Any>();
public enum class Tag {
ATND,
}
public fun register(tag: Tag, model: Any): Unit {
showcase.put(tag, model)
}
public fun get(tag: Tag): Any = showcase[tag]!!
}
}
- アプリ起動時にAtndApiのモデルをセットしておく
ModelLocator.register(ModelLocator.Companion.Tag.ATND, AtndModel());
- entities/AtndEntity
- Kotlinのデータクラスを使って作成
package me.rei_m.kotlinmvcsample.entities
import java.io.Serializable
Atndのイベント情報を保持するデータクラス.
public data class AtndEventEntity(val id: Int, val title: String) : Serializable
- network/AtndApi
- APIとの通信部分を作成。さんぷるなのでキーワードは固定。
package me.rei_m.kotlinmvcsample.network
import com.squareup.okhttp.CacheControl
import com.squareup.okhttp.HttpUrl
import com.squareup.okhttp.OkHttpClient
import com.squareup.okhttp.Request
import me.rei_m.kotlinmvcsample.entities.AtndEventEntity
import org.json.JSONObject
import rx.Observable
import java.net.HttpURLConnection
Atnd Apiへの接続部分を定義する.
public final class AtndApi private constructor() {
companion object {
Atnd APIへのリクエストを行いAtndEventのEntityを返すObservableを作成する.
public fun request(): Observable<AtndEventEntity> {
return Observable.create({ t ->
val url = HttpUrl.Builder()
.scheme("https")
.host("api.atnd.org")
.addPathSegment("events")
.addQueryParameter("keyword_or", "google,cloud")
.addQueryParameter("format", "json")
.build()
val request = Request.Builder()
.url(url)
.cacheControl(CacheControl.FORCE_NETWORK)
.build()
val response = OkHttpClient().newCall(request).execute()
if (response.code() == HttpURLConnection.HTTP_OK) {
val responseJson = JSONObject(response.body().string());
val eventCount = responseJson.getInt("results_returned")
if (0 < eventCount) {
val events = responseJson.getJSONArray("events")
for (i in 0..eventCount - 1) {
val e = events.getJSONObject(i).getJSONObject("event");
t.onNext(AtndEventEntity(e.getInt("event_id"), e.getString("title")))
}
}
} else {
t.onError(Throwable(response.code().toString()))
}
t.onCompleted()
})
}
}
}
package me.rei_m.kotlinmvcsample.models
import me.rei_m.kotlinmvcsample.entities.AtndEventEntity
import me.rei_m.kotlinmvcsample.events.AtndLoadedEvent
import me.rei_m.kotlinmvcsample.events.EventBusHolder
import me.rei_m.kotlinmvcsample.network.AtndApi
import rx.Observer
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import java.util.*
import me.rei_m.kotlinmvcsample.events.AtndLoadedEvent.Companion.Type as EventType
Atndに関する情報を管理するドメインモデル.
public class AtndModel {
API読込中など処理中か判定するフラグ
public var isBusy = false
private set
アテンドのイベント情報を保持するリスト
public val atndEventList = ArrayList<AtndEventEntity>()
AtndのAPIからイベント情報を取得し、内部に保持する.
public fun fetch() {
if (isBusy) {
return
}
isBusy = true
val observer = object : Observer<AtndEventEntity> {
override fun onNext(t: AtndEventEntity?) {
atndEventList.add(t!!)
}
override fun onCompleted() {
EventBusHolder.EVENT_BUS.post(AtndLoadedEvent(EventType.COMPLETE))
}
override fun onError(e: Throwable?) {
EventBusHolder.EVENT_BUS.post(AtndLoadedEvent(EventType.ERROR))
}
}
AtndApi.request()
.onBackpressureBuffer()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.finallyDo({
isBusy = false
})
.subscribe(observer)
}
}
- fragments/AtndEventListFragment
package me.rei_m.kotlinmvcsample.fragments
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ListView
import com.squareup.otto.Subscribe
import me.rei_m.kotlinmvcsample.R
import me.rei_m.kotlinmvcsample.entities.AtndEventEntity
import me.rei_m.kotlinmvcsample.events.AtndEventClickEvent
import me.rei_m.kotlinmvcsample.events.AtndLoadedEvent
import me.rei_m.kotlinmvcsample.events.EventBusHolder
import me.rei_m.kotlinmvcsample.managers.ModelLocator
import me.rei_m.kotlinmvcsample.models.AtndModel
import me.rei_m.kotlinmvcsample.views.adaptors.AtndEventListAdapter
import me.rei_m.kotlinmvcsample.events.AtndLoadedEvent.Companion.Type as EventType
import me.rei_m.kotlinmvcsample.managers.ModelLocator.Companion.Tag as ModelTag
AtndEventをリスト表示するFragment.
public class AtndEventListFragment : Fragment() {
ListViewのアダプター
private var mListAdapter: AtndEventListAdapter? = null
companion object {
ファクトリ.
fun newInstance(): AtndEventListFragment {
return AtndEventListFragment()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mListAdapter = AtndEventListAdapter(activity, R.layout.list_item_atnd_event)
}
override fun onDestroy() {
super.onDestroy()
mListAdapter = null
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater!!.inflate(R.layout.fragment_atnd_event_list, container, false)
val listView = view.findViewById(R.id.list_atnd_event) as ListView
listView.onItemClickListener = AdapterView.OnItemClickListener { parent,
view,
position,
id ->
val atndEventEntity = parent?.adapter?.getItem(position) as AtndEventEntity
EventBusHolder.EVENT_BUS.post(AtndEventClickEvent(atndEventEntity))
}
listView.adapter = mListAdapter
return view
}
override fun onResume() {
super.onResume()
EventBusHolder.EVENT_BUS.register(this);
val atndModel = ModelLocator.get(ModelTag.ATND) as AtndModel
val displayedCount = mListAdapter?.count!!
if (displayedCount != atndModel.atndEventList.size) {
mListAdapter?.clear()
mListAdapter?.addAll(atndModel.atndEventList)
mListAdapter?.notifyDataSetChanged()
} else if (displayedCount === 0) {
atndModel.fetch()
}
}
override fun onPause() {
super.onPause()
EventBusHolder.EVENT_BUS.unregister(this);
}
@Subscribe
@SuppressWarnings("unused")
public fun onAtndEventLoaded(event: AtndLoadedEvent) {
when (event.type) {
EventType.COMPLETE -> {
val atndModel = ModelLocator.get(ModelTag.ATND) as AtndModel
mListAdapter?.clear()
mListAdapter?.addAll(atndModel.atndEventList)
mListAdapter?.notifyDataSetChanged()
}
EventType.ERROR -> {
}
}
}
}
動かしてみる
GitHub - rei-m/Kotlin_mvc_sample
- Retrofitとか使うともっと短く書けるのかな。まだまだAndroid歴浅いのでなんとも修行足りない感。
- ちゃんとテストを書かないとというところがまだ考えきれてないのでその辺もやらないとですね。Stateパターンとかも取り入れてみたら規模がでかくなったら幸せになる気も。しばらく触りそうなので色々試してみます。