Androidプロジェクトに取り組み始めています。その構造をできるだけ堅牢にしたいと考えています。
私はWPF MVVMのバックグラウンドから来ており、Androidアプリケーションアーキテクチャについて少し読んでいますが、どのアーキテクチャを使用するべきかについて明確な答えを見つけることができませんでした。
一部の人々はMVVMの使用を提案しました- http://vladnevzorov.com/2011/04/30/Android-application-architecture-part-ii-architectural-styles-and-patterns/
他の人たちはMVCの使用を提案しましたが、それをどのように実装するべきかを正確に指定していませんでした。
私が言ったように、私はWPF-MVVMのバックグラウンドから来ているので、私が理解している限り、Androidではデフォルトでサポートされていないバインディングに大きく依存しています。
サードパーティのソリューションがあるようです- http://code.google.com/p/Android-binding/ しかし、それを信頼するかどうかはわかりません。開発が停止し、将来のAPIなどでサポートされなくなるとしたらどうでしょう。
基本的に私が探しているのは、アプリケーションの構造を構築するためのベストプラクティスを教えてくれる完全なチュートリアルです。フォルダーやクラスの構造など。完全なチュートリアルを見つけることができなかっただけで、Googleがそのようなチュートリアルを開発者に提供することを期待していました。この種類のドキュメントが技術的な側面を十分に処理するとは思いません- http://developer.Android.com/guide/topics/fundamentals.html
私が十分に明確で、あまり多くを求めていないことを願っています。コードがスパゲッティモンスターになる前に、アプリケーションの構造を確認したいだけです。
ありがとう!
まず、Androidを使用すると、任意のアーキテクチャを使用する必要がなくなります。それだけでなく、どのアーキテクチャにも従おうとするのがやや難しくなります。これには、賢い開発者である必要がありますスパゲッティコードベースの作成を避けるために:)
あなたはあなたが知っているそしてあなたが好きなどんなパターンにも合うように試みることができます。アプリケーションを開発するにつれて、最良のアプローチは何らかの形であなたの内臓に入ることがわかります(申し訳ありませんが、いつものように、正しく実行し始めるまでに多くの間違いを犯す必要があります)。
あなたが知っているパターンについて、私が何か間違ったことをさせてください:私は3つの異なるパターンを混ぜて、Androidで何が行われているのかを感じてもらいます。 Presenter/ModelViewはフラグメントまたはアクティビティのどこかにあるはずです。アダプターは、リスト内の入力を処理するため、この作業を行うことがあります。おそらくアクティビティもコントローラーのように機能するはずです。モデルは通常のJavaファイルである必要がありますが、ビューはレイアウトリソースと、実装する必要がある一部のカスタムコンポーネントを配置する必要があります。
ヒントをいくつかあげましょう。 これはコミュニティwikiの回答ですですから、他の人が他の提案を含めることを望みます。
主に2つの賢明な可能性があると思います。
個人的には、最初のアプローチを使用してプロジェクトに携わってきただけですが、物事をより組織化できると信じているので、実際に後者を試してみたいと思います。 30個の関連のないファイルを含むフォルダを作成してもメリットはありませんが、それが最初のアプローチで得られるものです。
したがって、「ViewPost」のコンテキストで使用されるすべての文字列、スタイル、IDは、「@ id/view_post_heading」(たとえば、テキストビューの場合)、「@ style/view_post_heading_style」、「@ string/view_post_greeting」で始まる必要があります。
これにより、オートコンプリート、編成が最適化され、名前の衝突などが回避されます。
アダプタ、アクティビティ、フラグメント、サービスなど、ほとんどすべての場合に基本クラスを使用する必要があると思います。これらは、少なくともデバッグ目的で役立ち、すべてのアクティビティで発生しているイベントを知ることができます。
Android=例を使用してMVVMを説明する方が役立つと思います。GitHubリポジトリ情報を含む完全な記事は、詳細については here です。
このシリーズの最初のパートで紹介した同じベンチマーク映画アプリの例を考えてみましょう。ユーザーが映画の検索語句を入力して[検索]ボタンを押すと、アプリはその検索語句を含む映画のリストを検索して表示します。リストの各映画をクリックすると、その詳細が表示されます。
次に、このアプリがMVVMにどのように実装されているかを説明し、続いて完全なAndroidアプリ、 my GitHubページ で入手できます)を説明します。
ユーザーがビューの[検索]ボタンをクリックすると、引数として検索用語を使用して、ViewModelからメソッドが呼び出されます。
main_activity_button.setOnClickListener({
showProgressBar()
mMainViewModel.findAddress(main_activity_editText.text.toString())
})
次に、ViewModelはモデルからfindAddress
メソッドを呼び出して、映画名を検索します。
fun findAddress(address: String) {
val disposable: Disposable = mainModel.fetchAddress(address)!!.subscribeOn(schedulersWrapper.io()).observeOn(schedulersWrapper.main()).subscribeWith(object : DisposableSingleObserver<List<MainModel.ResultEntity>?>() {
override fun onSuccess(t: List<MainModel.ResultEntity>) {
entityList = t
resultListObservable.onNext(fetchItemTextFrom(t))
}
override fun onError(e: Throwable) {
resultListErrorObservable.onNext(e as HttpException)
}
})
compositeDisposable.add(disposable)
}
応答がモデルからのものである場合、RxJavaオブザーバーのonSuccessメソッドは成功した結果を運びますが、ViewModelはビューにとらわれないため、表示するために結果を渡すためのビューインスタンスを持たないか、使用しません。代わりに、ビューで監視されるresultListObservable.onNext(fetchItemTextFrom(t))を呼び出すことにより、resultListObservableでイベントをトリガーします。
mMainViewModel.resultListObservable.subscribe({
hideProgressBar()
updateMovieList(it)
})
したがって、オブザーバブルはViewとViewModelの間で仲介役を果たします。
これがビューの完全なコードです。この例では、ViewはActivityクラスですが、Fragmentも同様に使用できます。
class MainActivity : AppCompatActivity() {
private lateinit var mMainViewModel: MainViewModel
private lateinit var addressAdapter: AddressAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mMainViewModel = MainViewModel(MainModel())
loadView()
respondToClicks()
listenToObservables()
}
private fun listenToObservables() {
mMainViewModel.itemObservable.subscribe(Consumer { goToDetailActivity(it) })
mMainViewModel.resultListObservable.subscribe(Consumer {
hideProgressBar()
updateMovieList(it)
})
mMainViewModel.resultListErrorObservable.subscribe(Consumer {
hideProgressBar()
showErrorMessage(it.message())
})
}
private fun loadView() {
setContentView(R.layout.activity_main)
addressAdapter = AddressAdapter()
main_activity_recyclerView.adapter = addressAdapter
}
private fun respondToClicks() {
main_activity_button.setOnClickListener({
showProgressBar()
mMainViewModel.findAddress(main_activity_editText.text.toString())
})
addressAdapter setItemClickMethod {
mMainViewModel.doOnItemClick(it)
}
}
fun showProgressBar() {
main_activity_progress_bar.visibility = View.VISIBLE
}
fun hideProgressBar() {
main_activity_progress_bar.visibility = View.GONE
}
fun showErrorMessage(errorMsg: String) {
Toast.makeText(this, "Error retrieving data: $errorMsg", Toast.LENGTH_SHORT).show()
}
override fun onStop() {
super.onStop()
mMainViewModel.cancelNetworkConnections()
}
fun updateMovieList(t: List<String>) {
addressAdapter.updateList(t)
addressAdapter.notifyDataSetChanged()
}
fun goToDetailActivity(item: MainModel.ResultEntity) {
var bundle = Bundle()
bundle.putString(DetailActivity.Constants.RATING, item.rating)
bundle.putString(DetailActivity.Constants.TITLE, item.title)
bundle.putString(DetailActivity.Constants.YEAR, item.year)
bundle.putString(DetailActivity.Constants.DATE, item.date)
var intent = Intent(this, DetailActivity::class.Java)
intent.putExtras(bundle)
startActivity(intent)
}
class AddressAdapter : RecyclerView.Adapter<AddressAdapter.Holder>() {
var mList: List<String> = arrayListOf()
private lateinit var mOnClick: (position: Int) -> Unit
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): Holder {
val view = LayoutInflater.from(parent!!.context).inflate(R.layout.item, parent, false)
return Holder(view)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.itemView.item_textView.text = mList[position]
holder.itemView.setOnClickListener { mOnClick(position) }
}
override fun getItemCount(): Int {
return mList.size
}
infix fun setItemClickMethod(onClick: (position: Int) -> Unit) {
this.mOnClick = onClick
}
fun updateList(list: List<String>) {
mList = list
}
class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView)
}
}
これがViewModelです:
class MainViewModel() {
lateinit var resultListObservable: PublishSubject<List<String>>
lateinit var resultListErrorObservable: PublishSubject<HttpException>
lateinit var itemObservable: PublishSubject<MainModel.ResultEntity>
private lateinit var entityList: List<MainModel.ResultEntity>
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
private lateinit var mainModel: MainModel
private val schedulersWrapper = SchedulersWrapper()
constructor(mMainModel: MainModel) : this() {
mainModel = mMainModel
resultListObservable = PublishSubject.create()
resultListErrorObservable = PublishSubject.create()
itemObservable = PublishSubject.create()
}
fun findAddress(address: String) {
val disposable: Disposable = mainModel.fetchAddress(address)!!.subscribeOn(schedulersWrapper.io()).observeOn(schedulersWrapper.main()).subscribeWith(object : DisposableSingleObserver<List<MainModel.ResultEntity>?>() {
override fun onSuccess(t: List<MainModel.ResultEntity>) {
entityList = t
resultListObservable.onNext(fetchItemTextFrom(t))
}
override fun onError(e: Throwable) {
resultListErrorObservable.onNext(e as HttpException)
}
})
compositeDisposable.add(disposable)
}
fun cancelNetworkConnections() {
compositeDisposable.clear()
}
private fun fetchItemTextFrom(it: List<MainModel.ResultEntity>): ArrayList<String> {
val li = arrayListOf<String>()
for (resultEntity in it) {
li.add("${resultEntity.year}: ${resultEntity.title}")
}
return li
}
fun doOnItemClick(position: Int) {
itemObservable.onNext(entityList[position])
}
}
そして最後にモデル:
class MainModel {
private var mRetrofit: Retrofit? = null
fun fetchAddress(address: String): Single<List<MainModel.ResultEntity>>? {
return getRetrofit()?.create(MainModel.AddressService::class.Java)?.fetchLocationFromServer(address)
}
private fun getRetrofit(): Retrofit? {
if (mRetrofit == null) {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder().addInterceptor(loggingInterceptor).build()
mRetrofit = Retrofit.Builder().baseUrl("http://bechdeltest.com/api/v1/").addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJava2CallAdapterFactory.create()).client(client).build()
}
return mRetrofit
}
class ResultEntity(val title: String, val rating: String, val date: String, val year: String)
interface AddressService {
@GET("getMoviesByTitle")
fun fetchLocationFromServer(@Query("title") title: String): Single<List<ResultEntity>>
}
}
完全な記事 ここ