Kotlin for AndroidでMVPアプリケーションを作成しています









Android向けのKotlinでのアプリケーション開発は、開発者の間で人気を集めていますが、インターネットのロシア語セグメントにはほとんど記事がありません。 状況を少し修正して、Kotlinでのアプリケーション開発に関するチュートリアルを作成することにしました。 Android開発世界のすべてのトレンドライブラリ(RxJavaを除く)を使用して、完全なアプリケーションを作成します。 最終的に、拡張可能で簡単にテスト可能なアプリケーションを取得する必要があります(テストを自分で作成しません)。



注意! この記事では、アプリケーションバージョン1.0の作成方法について説明します。 リポジトリ内の現在のコードは、記事で説明されているものと異なる場合があります。



おそらく、プログラミング言語Kotlinに加えて、JetBrainsは、従来のXMLファイルの代わりとして、UIアプリケーションを作成するためにAnkoライブラリも開発していることをご存じでしょう。 Ankoに慣れていない人を困難な立場に置かないために、プロジェクトでは使用しません。



内容:







Android Studioのセットアップ





Kotlin言語でアプリケーションを作成するには、Android Studioに特別なプラグインが必要です。 プラグインのインストール手順については、 こちらをご覧ください 。 また、現時点ではKotlinプラグインでサポートされていないため、Android Studioの設定で「インスタントラン」機能を無効にすることを忘れないでください。



コードを正しく生成するには、バージョン1.0.1以上のプラグインを使用する必要があります。 Kotlinバージョン1.0.2 EAPを使用しました。 これは、アプリケーションのbuild.gradleファイルが私のプロジェクトでどのように見えるかです:



apply plugin: 'com.android.application' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { applicationId "imangazaliev.notelin" minSdkVersion 15 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main.java.srcDirs += 'src/main/kotlin' androidTest.java.srcDirs += 'src/androidTest/kotlin' } } dependencies { ... } kapt { generateStubs = true } buildscript { ext.kotlin_version = '1.0.2-eap-15' repositories { mavenCentral() maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } repositories { mavenCentral() }
      
      









何を書きますか?





だから、最初に書くものを決める必要がありますか? 考え直すことなく、私はメモアプリケーションに落ち着きました。 名前も簡単に考えられました-Notelin。 アプリケーションは非常にシンプルで、2つの画面で構成されています。



-ホーム画面-メモ付きのリストが含まれています

-ノート画面-ここでは、選択したノートの内容を表示/編集できます



アプリケーション要件はわずかです。



-メモの追加/表示/削除

-メモ情報を表示する

-タイトルと日付でメモを並べ替える

-ノートヘッダーの検索



使用するライブラリ





データベースを操作するには、 Android Activeライブラリを使用します。 このリンクで作業するためのレッスンを見つけることができます。 Dagger 2ライブラリーはDepency Injectionの実装に使用され、HabréonHabréに関する多くの記事があります。 アプリケーション全体の基礎はMoxyライブラリになります。 その助けを借りて、プロジェクトにMVPパターンを実装します。 ライフサイクルの問題を完全に解決するため、アプリケーションのコンポーネントを再作成することを心配する必要はありません。 また、Android- KAndroidの Kotlin言語の拡張機能セットも使用します。 途中で残りのライブラリについてお話します。



以下は、プロジェクトの依存関係のリストです。



 allprojects { repositories { jcenter() mavenCentral() maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } maven { url "https://mvn.arello-mobile.com/" } maven { url "https://jitpack.io" } maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' } } }
      
      







アプリケーションの依存関係のリストは次のとおりです。



 dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "com.android.support:appcompat-v7:23.1.1" compile 'com.android.support:recyclerview-v7:23.1.1' compile 'com.android.support:cardview-v7:23.1.1' //   Android Kotlin compile 'com.pawegio.kandroid:kandroid:0.5.0@aar' //ActiveAndroid DB compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT' //FAB compile 'com.melnykov:floatingactionbutton:1.3.0' //MaterialDialog compile 'com.github.afollestad.material-dialogs:core:0.8.5.6@aar' //MVP compile 'com.arello-mobile:moxy:0.4.2' compile 'com.arello-mobile:moxy-android:0.4.2' kapt 'com.arello-mobile:moxy-compiler:0.4.2' //RX compile 'io.reactivex:rxjava:1.1.0' compile 'io.reactivex:rxandroid:1.1.0' //Depency Injection kapt 'com.google.dagger:dagger-compiler:2.0.2' compile 'com.google.dagger:dagger:2.0.2' provided 'org.glassfish:javax.annotation:10.0-b28' //EventBus compile 'org.greenrobot:eventbus:3.0.0' }
      
      







aptではなくkaptを使用していることに注意してください。 これは、Kotlin要素に注釈を付けることができるGradleのプラグインです。



アプリケーション構造





最終バージョンのプロジェクトの構造は次のとおりです。







モデルを作成





メモには4つのフィールドがあります。







これをすべてコードに実装します。



 class Note : Model { @Column(name = "title") public var title: String? = null @Column(name = "text") public var text: String? = null @Column(name = "create_date") public var createDate: Date? = null @Column(name = "change_date") public var changeDate: Date? = null constructor(title: String, createDate: Date, changeDate: Date) { this.title = title this.createDate = createDate this.changeDate = changeDate } constructor() fun getInfo(): String = ":\n$title\n" + " :\n${formatDate(createDate)}\n" + " :\n${formatDate(changeDate)}"; }
      
      







このモデルによれば、ActiveAndroidライブラリは、ノートが保存されるデータベースを作成します。 お気づきの方は、空とパラメーター付きの2つのコンストラクターがあります。 最初のコンストラクターと2番目のコンストラクターであるActiveAndroidを使用します。 モデルはModelクラスから継承されるため、次のようにsave()およびdelete()メソッドを呼び出すだけで、ノートを保存および削除できます。



 var note = Note(" ", Date()) note.save() ... note.delete()
      
      







しかし、モデルを使用する前に、マニフェストファイルにいくつかのメタデータを書き込む必要があります。



 <meta-data android:name="AA_DB_NAME" android:value="Notelin.db" /> <meta-data android:name="AA_DB_VERSION" android:value="1" />
      
      







コメントなしですべてが明確だと思います。 com.activeandroid.app.ApplicationからApplicationクラスを継承します:



 class NotelinApplication : Application() { ... }
      
      







アプリケーションがデータベースに依存しにくくするために、モデルにNoteDaoラッパーを作成しました。 このラッパーでは、ノートの作成、保存、更新、削除のすべての操作が行われます。



 class NoteDao { /** *    */ fun createNote(): Note { var note = Note(" ", Date()) note.save() return note } /** *     */ fun saveNote(note: Note) = note.save() /** *        View */ fun loadAllNotes() = Select().from(Note::class.java).execute<Note>() /** *    id    */ fun getNoteById(noteId: Long) = Select().from(Note::class.java).where("id = ?", noteId).executeSingle<Note>() /** *     */ fun deleteAllNotes() { Delete().from(Note::class.java).execute<Note>(); } /** *    id */ fun deleteNote(note: Note) { note.delete() } }
      
      







おそらく、オブジェクトの作成に新しいキーワードを使用しなかったことにお気付きでしょう。これがKotlinとJavaの違いです。





ノート画面





また、アプリケーションのメイン画面でもあります。 その上で、ユーザーはメモを追加/削除したり、メモに関する情報を表示したり、日付や名前でソートしたり、タイトルで検索したりできます。





MainViewおよびMainPresenterを作成します









次に、これらすべてをコードに変換する必要があります。 まず、ビューのインターフェースを作成します。



 @StateStrategyType(value = AddToEndSingleStrategy::class) interface MainView : MvpView { fun onNotesLoaded(notes: List<Note>) fun updateView() fun onSearchResult(notes: List<Note>) fun onAllNotesDeleted() fun onNoteDeleted() fun showNoteInfoDialog(noteInfo: String) fun hideNoteInfoDialog() fun showNoteDeleteDialog(notePosition: Int) fun hideNoteDeleteDialog() fun showNoteContextDialog(notePosition: Int) fun hideNoteContextDialog() }
      
      







次に、作成したインターフェイスをアクティビティに実装します。



 class MainActivity : MvpAppCompatActivity(), MainView {
      
      







Kotlinの機能の1つは、インターフェイスの継承と実装がクラス名の後にコロンで示されることです。 また、インターフェイスの前にある親クラスの名前は重要ではありません。インターフェイスの後でも、またはインターフェイスの間でも、リストにはクラスが1つしかありません。 つまり、上記のエントリは次のようになります。



 class MainActivity : MainView, MvpAppCompatActivity() {
      
      







コンマで区切られた別のクラス名を追加しようとすると、IDEはエラーをスローし、赤い線で2番目に来るクラスの名前に下線を引きます。



ここでは、メソッドを空のままにします。 ご覧のとおり、アクティビティはMvpAppCompatActivityから継承されます 。 これは、画面が回転したときにアクティビティが状態を復元できるようにするために必要です。



プレゼンタークラスを作成します。



 @InjectViewState class MainPresenter: MvpPresenter<MainView>() { }
      
      







プレゼンターは、どのビューで作業するかを示すMvpPresenterからも継承されます。 これを行うには、モジュールを作成します-サプライヤNoteDao:



 @Module class NoteDaoModule { @Provides @Singleton fun provideNoteDao() : NoteDao= NoteDao() }
      
      







プレゼンターを注入するためのコンポーネントを作成します。



 @Singleton @Component(modules = arrayOf(NoteDaoModule::class)) interface AppComponent { fun inject(mainPresenter : MainPresenter) }
      
      







ここで、ApplicationクラスにAppComponentクラスの静的インスタンスを作成する必要があります。



 class NotelinApplication : Application() { companion object { lateinit var graph: AppComponent } override fun onCreate() { super.onCreate() graph = DaggerAppComponent.builder().noteDaoModule(NoteDaoModule()).build() } }
      
      







これで、モデルをプレゼンターに挿入できます。



 @InjectViewState class MainPresenter : MvpPresenter<MainView>() { @Inject lateinit var mNoteDao: NoteDao init { NotelinApplication.graph.inject(this) } }
      
      







MainViewとMainPresenterの相互作用のために、MainActivityで変数を作成する必要があります。



 @InjectPresenter lateinit var mPresenter: MainPresenter
      
      







Moxyプラグイン自体は、ビューをフラグメントにバインドし、他の必要なアクションを実行します。



リストとフローティングボタンで画面レイアウトを作成します。 Activity_main.xmlファイル:



 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.activities.MainActivity"> <TextView android:id="@+id/tvNotesIsEmpty" android:layout_width="match_parent" android:layout_height="match_parent" android:text="@string/notes_is_empty" android:gravity="center" /> <android.support.v7.widget.RecyclerView android:id="@+id/rvNotesList" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="vertical" app:layoutManager="android.support.v7.widget.LinearLayoutManager" /> <com.melnykov.fab.FloatingActionButton android:id="@+id/fabButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|right" android:layout_margin="16dp" android:src="@mipmap/ic_add" app:fab_colorNormal="@color/colorPrimary" app:fab_colorPressed="@color/colorPrimaryDark" /> </FrameLayout>
      
      







フライングボタンを実装するために、 FloatingActionButtonライブラリを使用しました。 Googleは既にFABをサポートライブラリに追加しているため、ソリューションを使用できます。



どのレイアウトを表示するかをアクティビティに示します。



 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) }
      
      







次に、FABとリストをリンクして、リストが上にスクロールしたときにボタンが消えるようにする必要があります。



 fabButon.attachToRecyclerView(rvNotesList)
      
      







退屈なfindViewByIdを順番に記述する必要はありません。importを使用してブロックに1行だけ記述するだけです。



 import kotlinx.android.synthetic.main.activity_main.*
      
      







ご覧のとおり、最後のパッケージはxmlファイルの名前と一致しています。 IDEはビューのプロパティを自動的に初期化し、それらの名前はマークアップで指定したIDと一致します。



データベースからメモをロードしましょう。 メモは一度だけダウンロードして、後で使用する必要があります。 MvpPresenterクラスのonFirstViewAttachメソッドはこれに役立ちます。これは、Viewがプレゼンターに初めてバインドされたときに呼び出されます。 さらに、アクティビティをいくら回転させても、データはプレゼンターにキャッシュされます。



 override fun onFirstViewAttach() { super.onFirstViewAttach() loadAllNotes() } /** *        View */ fun loadAllNotes() { mNotesList = mNoteDao.loadAllNotes() viewState.onNotesLoaded(mNotesList) }
      
      







リスト用のアダプターを作成します。



アダプターコード
 class NotesAdapter : RecyclerView.Adapter<NotesAdapter.ViewHolder> { private var mNotesList: List<Note> = ArrayList() constructor(notesList: List<Note>) { mNotesList = notesList } /** *   View  ViewHolder  ,    . */ override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ViewHolder { var v = LayoutInflater.from(viewGroup.context).inflate(R.layout.note_item_layout, viewGroup, false); return ViewHolder(v); } /** *   View       i */ override fun onBindViewHolder(viewHolder: ViewHolder, i: Int) { var note = mNotesList[i]; viewHolder.mNoteTitle.text = note.title; viewHolder.mNoteDate.text = formatDate(note.changeDate); } override fun getItemCount(): Int { return mNotesList.size } /** *   ViewHolder,    . */ class ViewHolder : RecyclerView.ViewHolder { var mNoteTitle: TextView var mNoteDate: TextView constructor(itemView: View) : super(itemView) { mNoteTitle = itemView.findViewById(R.id.tvItemNoteTitle) as TextView mNoteDate = itemView.findViewById(R.id.tvItemNoteDate) as TextView } } }
      
      









アダプターでは、formatDateメソッドを使用します。 日付を文字列にフォーマットするのに役立ちます:



 @file:JvmName("DateUtils") package imangazaliev.notelin.utils import java.text.SimpleDateFormat import java.util.* fun formatDate(date: Date?): String { var dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm"); return dateFormat.format(date) }
      
      







このメソッドはDateUtils.ktファイルにあり、通常の静的メソッドとして使用できます。 ここでの静的メソッドとの違いは、メソッドがクラスに属しているのではなく、パッケージに属しているため、メソッド名の前にクラス名を記述する必要がないことです。 アノテーションでは、Javaからメソッドにアクセスするクラスの名前を示します。 たとえば、Javaでは、メロンメソッドは次のように呼び出されます。



 String formattedDate = DateUtils.formatDate(date);
      
      







アクティビティのonNotesLoadedメソッドで、メモを表示します。



 override fun onNotesLoaded(notes: List<Note>) { rvNotesList.adapter = NotesAdapter(notes) updateView() } override fun updateView() { rvNotesList.adapter.notifyDataSetChanged() if (rvNotesList.adapter.itemCount == 0) { rvNotesList.visibility = View.GONE tvNotesIsEmpty.visibility = View.VISIBLE } else { rvNotesList.visibility = View.VISIBLE tvNotesIsEmpty.visibility = View.GONE } }
      
      







メモがない場合は、TextViewに「メモなし」というメッセージを表示します。



私の知る限り、RecycleView要素のクリックを処理する「公式」のOnItemClickListenerはありません。 したがって、ソリューションを使用します。



ItemClickSupport.kt
 class ItemClickSupport private constructor(private val recyclerView: RecyclerView) { companion object { fun addTo(view: RecyclerView) = view.getTag(R.id.item_click_support) as? ItemClickSupport ?: ItemClickSupport(view) fun removeFrom(view: RecyclerView): ItemClickSupport? { val support = view.getTag(R.id.item_click_support) as? ItemClickSupport support?.detach(view) return support } } interface ItemClickListener { fun onClick(recyclerView: RecyclerView, position: Int, view: View) } interface ItemLongClickListener { fun onLongClick(recyclerView: RecyclerView, position: Int, view: View): Boolean } private var itemClickListener: ItemClickListener? = null private var itemLongClickListener: ItemLongClickListener? = null private val onClickListener = View.OnClickListener { val holder = recyclerView.getChildViewHolder(it) itemClickListener?.onClick(recyclerView, holder.adapterPosition, it) } private val onLongClickListener = View.OnLongClickListener listener@{ val position = recyclerView.getChildViewHolder(it).adapterPosition return@listener itemLongClickListener?.onLongClick(recyclerView, position, it) ?: false } private val attachListener = object : RecyclerView.OnChildAttachStateChangeListener { override fun onChildViewAttachedToWindow(view: View) { if (itemClickListener != null) { view.setOnClickListener(onClickListener) } if (itemLongClickListener != null) { view.setOnLongClickListener(onLongClickListener) } } override fun onChildViewDetachedFromWindow(view: View) = Unit } init { recyclerView.setTag(R.id.item_click_support, this) recyclerView.addOnChildAttachStateChangeListener(attachListener) } fun setOnItemClickListener(listener: ItemClickListener): ItemClickSupport { itemClickListener = listener return this } fun setOnItemClickListener(listener: (RecyclerView, Int, View) -> Unit) = setOnItemClickListener(object : ItemClickListener { override fun onClick(recyclerView: RecyclerView, position: Int, view: View) { listener(recyclerView, position, view) } }) fun setOnItemLongClickListener(listener: ItemLongClickListener): ItemClickSupport { itemLongClickListener = listener return this } fun setOnItemLongClickListener(block: (RecyclerView, Int, View) -> Boolean): ItemClickSupport = setOnItemLongClickListener(object : ItemLongClickListener { override fun onLongClick(recyclerView: RecyclerView, position: Int, view: View): Boolean { return block (recyclerView, position, view) } }) private fun detach(view: RecyclerView) { view.removeOnChildAttachStateChangeListener(attachListener) view.setTag(R.id.item_click_support, null) } }
      
      









アクティビティのonCreateメソッドで、次のように記述します。



  with(ItemClickSupport.addTo(rvNotesList)) { setOnItemClickListener { recyclerView, position, v -> mPresenter.openNote(this@MainActivity, position) } setOnItemLongClickListener { recyclerView, position, v -> mPresenter.showNoteContextDialog(position); true } }
      
      







with関数を使用すると、変数の名前を毎回書き込むのではなく、渡したオブジェクトのメソッドを呼び出すことしかできません。 アクティビティを取得するために、 これだけでなく、 この@ MainActivityを使用したことに注意してください。 これは、withブロックでこれを使用すると、with関数に渡したオブジェクトが返されるためです。 通常のアイテムをクリックすると、アクティビティに移動し、メモのテキストを表示できます。 長押しすると、コンテキストメニューが表示されます。 お気付きなら、閉じ括弧の前にreturnという単語を書いていません。 これは間違いではなく、Kotlin言語の機能です。



プレゼンターでメニュー項目をクリックすると、次のようになります。



 /** *       */ fun openNote(activity: Activity, position: Int) { val intent = Intent(activity, NoteActivity::class.java) intent.putExtra("note_id", mNotesList[position].id) activity.startActivity(intent) }
      
      







NoteActivityクラスはまだ作成されていないため、コンパイラーはエラーをスローします。 この問題を解決するには、NoteActivityクラスを作成するか、openNoteメソッド内のコードをコメントアウトします。 NoteActivity :: class.javaエントリは、JavaのNoteActivity.classに似ています。 また、get(位置)メソッドではなく、通常の配列のように角括弧を使用してリストにアクセスしていることに注意してください。



アプリケーションでMoxy MVPライブラリを使用する場合、ダイアログの表示/閉じるなど、Viewのすべてのアクションがプレゼンターを通過する必要があるという事実に慣れる必要があります。 最初は、これはあまり馴染みがなく不便ではありませんが、アクティビティを再作成してもダイアログボックスが消えないことが確実なため、メリットははるかに大きくなります。



 /** *     */ fun showNoteContextDialog(position: Int) { viewState.showNoteContextDialog(position) } /** *     */ fun hideNoteContextDialog() { viewState.hideNoteContextDialog() }
      
      







記事が非常に大きいため、コンテキストメニューのコードは表示せず、メモに関する情報を削除して表示します。 しかし、私はあなたが一般的な意味をつかんだと思います。 また、ダイアログが戻るボタンで閉じられたとき、またはダイアログの外側の領域をクリックしたときでも、プレゼンターのhideNoteContextDialogメソッドが呼び出されることに注意してください。



FABを押すと、新しいメモが作成されます。



 fabButton.setOnClickListener { mPresenter.openNewNote(this) }
      
      







新しいメモを作成するには、プレゼンターでopenNewNote関数を呼び出します。



 fun openNewNote(activity: Activity) { val newNote = mNoteDao.createNote() mNotesList.add(newNote) sortNotesBy(getCurrentSortMethod()) openNote(activity, mNotesList.indexOf(newNote)) }
      
      







openNewNoteメソッドは、以前に作成したopenNoteを使用します。これには、Contextとリスト内のノートの位置を渡します。





メモによる検索を実装します









ノート検索を追加しましょう。 res / menuフォルダーにmain.xmlファイルを作成します。



 <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_search" android:icon="@android:drawable/ic_menu_search" android:title="@string/search" app:actionViewClass="android.support.v7.widget.SearchView" app:showAsAction="always" /> </menu>
      
      







MainActivityでは、次のように記述します。



 override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.main, menu) initSearchView(menu) return true } private fun initSearchView(menu: Menu) { var searchViewMenuItem = menu.findItem(R.id.action_search); var searchView = searchViewMenuItem.actionView as SearchView; searchView.onQueryChange { query -> mPresenter.search(query) } searchView.setOnCloseListener { mPresenter.search(""); false } }
      
      







検索フィールドのテキストを変更する場合、文字列をフィールドからプレゼンターに転送し、結果をリストに表示します。 実際、SearchViewにはonQueryChangeメソッドがなく、KAndroidライブラリによって追加されました。



プレゼンターで検索を実装します。



 /** *     */ fun search(query: String) { if (query.equals("")) { viewState.onSearchResult(mNotesList) } else { val searchResults = mNotesList.filter { it.title!!.startsWith(query, ignoreCase = true) } viewState.onSearchResult(searchResults) } }
      
      







1行で、フィルターメソッドとラムダを使用してリスト検索を実装したことの美しさに注目してください。 Javaでは、同じ機能に6〜7行かかります。 検索結果を表示するために残ります:



 override fun onSearchResult(notes: List<Note>) { rvNotesList.adapter = NotesAdapter(notes) }
      
      









ソートノートを実装します





そして、メイン画面を作成する最後のステップは、ノートのソートです。 res / menu / main.xmlに次の行を追加します。



 <item android:title="@string/sort_by"> <menu> <item android:id="@+id/menuSortByName" android:title="@string/sort_by_title" /> <item android:id="@+id/menuSortByDate" android:title="@string/sort_by_date" /> </menu> </item>
      
      







次に、メニュー項目のクリックを処理する必要があります。



 override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.menuSortByName -> mPresenter.sortNotesBy(MainPresenter.SortNotesBy.NAME) R.id.menuSortByDate -> mPresenter.sortNotesBy(MainPresenter.SortNotesBy.DATE) } return super.onOptionsItemSelected(item) }
      
      







when文は、Javaのswitch-caseのより機能的な類似物です。 MainPresenterでコードを並べ替える:

 /** *   */ fun sortNotesBy(sortMethod: SortNotesBy) { mNotesList.sortWith(sortMethod) viewState.updateView() }
      
      









メモの内容を含む画面





ここで、メモの内容を含む画面を作成する必要があります。 ここで、ユーザーはメモのタイトルとテキストを表示/編集したり、保存または削除したり、メモに関する情報を表示したりできます。





NoteViewとNotePresenterを作成する









画面には3つのビューのみが含まれます。



-見出し

-最終変更日

-テキストノート



そして、ここにマークアップ自体があります:



 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <EditText android:id="@+id/etTitle" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:background="#EEEEEE" android:textColor="#212121" android:paddingLeft="10dp" android:paddingTop="10dp" android:paddingBottom="5dp" android:hint="" /> <TextView android:id="@+id/tvNoteDate" android:layout_width="match_parent" android:layout_height="wrap_content" android:singleLine="true" android:background="#EEEEEE" android:textColor="#212121" android:paddingLeft="10dp" android:paddingBottom="10dp" /> <EditText android:id="@+id/etText" android:layout_width="match_parent" android:layout_height="match_parent" android:textColor="#000000" android:gravity="start" android:hint="" android:background="@null" android:paddingLeft="10dp" /> </LinearLayout>
      
      







記事の冒頭で、Ankoについて簡単に言及しました。 このライブラリにより、可読性を損なうことなくコードを大幅に削減できます。 たとえば、Ankoを使用すると、マークアップは次のようになります。



 class MyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) MyActivityUI().setContentView(this) } } class MyActivityUI : AnkoComponent { companion object { val ET_TITLE_ID = View.generateViewId() val TV_NOTE_DATE_ID = View.generateViewId() val ET_TEXT_ID = View.generateViewId() } override fun createView(ui: AnkoContext<MainActivit>) = with(ui) { verticalLayout { editText { id = ET_TITLE_ID singleLine = true backgroundColor = 0xEE.gray.opaque textColor = 0x21.gray.opaque leftPadding = dip(10) topPadding = dip(10) bottomPadding = dip(5) hint = "" }.lparams(matchParent, wrapContent) textView { id = TV_NOTE_DATE_ID singleLine = true backgroundColor = 0xEE.gray.opaque textColor = 0x21.gray.opaque leftPadding = dip(10) bottomPadding = dip(10) }.lparams(matchParent, wrapContent) editText { id = ET_TEXT_ID textColor = Color.BLACK gravity = Gravity.START hint = "" background = null leftPadding = dip(10) } } } }
      
      







しかし、気を取られずにコードを書き始めましょう。 まず、ビューを作成する必要があります。



 interface NoteView : MvpView { fun showNote(note: Note) fun onNoteSaved() fun onNoteDeleted() fun showNoteInfoDialog(noteInfo: String) fun hideNoteInfoDialog() fun showNoteDeleteDialog() fun hideNoteDeleteDialog() }
      
      







NoteActivityでNoteViewを実装する:



 class NoteActivity : MvpAppCompatActivity(), NoteView { @InjectPresenter lateinit var mPresenter: NotePresenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_note) val noteId = intent.extras.getLong("note_id", -1) mPresenter.showNote(noteId) } }
      
      







onCreateでは、ノートIDを取得して、プレゼンターがデータベースからノートを取得し、データをビューに転送するようにします。 プレゼンターを作成します。



 @InjectViewState class NotePresenter : MvpPresenter<NoteView>() { @Inject lateinit var mNoteDao: NoteDao lateinit var mNote: Note init { NotelinApplication.graph.inject(this) } fun showNote(noteId: Long) { mNote = mNoteDao.getNoteById(noteId) viewState.showNote(mNote) } }
      
      







行をAppComponentクラスに忘れずに追加してください。



 fun inject(notePresenter: NotePresenter)
      
      







メモを表示:



 override fun showNote(note: Note) { tvNoteDate.text = DateUtils.formatDate(note.changeDate) etTitle.setText(note.title) etText.setText(note.text) }
      
      









メモの保存を実装する





メモを保存するには、メニューで適切なアイテムを選択する必要があります。 res / menu / note.xmlファイルを作成します



 <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" > <item android:id="@+id/menuSaveNote" android:title="@string/save" app:showAsAction="never" /> </menu>
      
      







 override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.note, menu) return true }    Activity: override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.menuSaveNote -> mPresenter.saveNote(etTitle.text.toString(), etText.text.toString()) } return super.onOptionsItemSelected(item) }
      
      







繰り返しますが、メモに関する情報を削除および表示するためのコードは引用していません。ソースコードを表示すると、ノート識別子に加えて、リスト内のノートの位置をNoteActivityに渡していることがわかります。これは、ノート表示画面でノートを削除すると、リストからも削除されるようにするために必要です。この機能を実装するために、EventBusを使用しました。そして再び、私はコードを引用しませんでした。



それだけです:メモが追加、編集、削除されます。ノートを検索およびソートすることもできます。すべてがどのように機能するかをよりよく理解するために、記事の最後に提供した完全なソースコード(リンク)を必ずご覧ください。





謝辞





もちろん、記事を書いてくれた人たちのことを忘れてはいけません。 habrayuzersのYuri Shmakov(@senneco)に、Moxyライブラリの支援やその他の問題の支援に感謝します。また、記事を確認し、Ankoのコードを提供してくれたJetBrainsの従業員であるRoman Belov(@belovrv)にも感謝します。



UPD: EPIC COMMITのSirikidにも感謝したいと思います。おかげで、Kotlinの機能を使用してコードの印象的な部分をやり直しました。





おわりに





この記事が、Kotlinでのアプリケーションの作成がJavaの場合よりも難しくなく、さらに簡単ではないことを納得させることを願っています。もちろん、JetBrainsの従業員がすぐに修正するバグもあります。質問がある場合は、Slackチャンネルで開発者に直接質問することができますKotlinの開発に関する記事もこちらで読むことができます



プロジェクトのソースコード:Notelin



All Articles