📜 ⬆️ ⬇️

We write MVP application on Kotlin for Android



Application development on Kotlin for Android is gaining popularity among developers, but there are few articles in the Russian-speaking segment of the Internet. I decided to tweak the situation a bit and write a tutorial on developing an application on Kotlin. We will write a full-fledged application using all trend libraries (except RxJava) in the world of Android development. In the end, we should have an extensible and easily testable application (we will not write the tests themselves).

Attention! This article describes how to create an application version 1.0 . The current code in the repository may differ from that described in the article.
')
Probably, some of you know that in addition to the programming language, Kotlin JetBrains also develops the Anko library, to create a UI application, as a replacement for regular XML files. We will not use it in our project in order not to embarrass people who are not familiar with Anko.

Content:



Setting up Android Studio



To write applications in the Kotlin language, Android Studio needs a special plugin. Installation instructions for the plugin can be found here . Also, do not forget to turn off the “Instant Run” feature in the Android Studio settings, since at the moment it is not supported by the Kotlin plugin.

For correct code generation, you need to use a version of the plugin not lower than 1.0.1. I used the Kotlin version 1.0.2 EAP . This is how the application's build.gradle file looks in my project:

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() } 



What are we going to write?



So, first we need to decide what we will write? Without thinking, I stopped at the application-notes. The name also came up easily - Notelin. The application is very simple and consists of two screens:

- Main screen - contains a list with notes
- Notes screen - here you can view / edit the content of the selected note

Application requirements are small:

- Add / view / delete notes
- View information about the note
- Sort notes by title and date
- Search by note headers

Used libraries



To work with the database, I will use the Android Active library. A lesson on working with it can be found at this link . To implement Depency Injection, the Dagger 2 library was used. On Habré there are many articles on working with it. The basis of the entire application will be the Moxy library. With its help, we implement the MVP pattern in our project. It completely solves the problems of the life cycle, so you can not worry about re-creating the components of your application. We will also use a set of extensions for the Kotlin language in Android - KAndroid . About the rest of the library, I will talk along the way.

Below is a list of project dependencies:

 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' } } } 


And here is the list of dependencies of the application:

 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' } 


Note that instead of apt I use kapt . This is a Gradle plugin that allows you to annotate Kotlin elements.

Application structure



Here is the structure of our project in the final version:



Create a Model



The notes will have four fields:



We implement all this in the code:

 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)}"; } 


According to this model, the ActiveAndroid library will create a database in which our notes will be stored. If you notice, we have two constructors: empty and with parameters. We will use the first constructor, and the second - ActiveAndroid. Our model is inherited from the Model class, so we can save and delete our notes simply by calling the save () and delete () methods, for example:

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


But before using our model, we need to register some meta data in the Manifest file:

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


I think everything is clear without comment. It remains to inherit the Application class from com.activeandroid.app.Application:

 class NotelinApplication : Application() { ... } 


To make the application less dependent on the database, I created a NoteDao wrapper on our model, in which all operations for creating, saving, updating and deleting notes will take place:

 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() } } 


You probably noticed that for creating objects we did not use the new keyword - this is the difference between Kotlin and Java.


Screen with notes



Also is the main screen of the application. On it, the user can add / delete a note, view information about the note, sort them by date or title, and also search by title.


Create a MainView and MainPresenter





Now we need to translate all this into code. First, create the interface for our View:

 @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() } 


Next, we will implement the created interface in our activit:

 class MainActivity : MvpAppCompatActivity(), MainView { 


One of the features of Kotlin is that the inheritance and implementation of interfaces is indicated by a colon after the class name. It also does not make any difference to the name of the parent class before the interfaces, after or even between them, as long as the class in the list is one. That is, the entry above could look like this:

 class MainActivity : MainView, MvpAppCompatActivity() { 


If you try to add the name of another class, separated by a comma, the IDE will give an error and underline the name of the class that follows the second line.

For now, leave the methods empty. As you can see, activit is inherited from MvpAppCompatActivity . This is necessary so that the activit could restore the state when the screen is rotated.

Create a class presenter:

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


A presenter is also inherited from MvpPresenter, to which we indicate which View we will work with. It remains to inject our model into the presenter. To do this, we create a module - supplier NoteDao:

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


Create a Component to inject the presenter:

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


Now we need to create a static instance of the AppComponent class in the Application class:

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


Now we can inject our model in the presenter:

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


For the interaction between MainView and MainPresenter, we need to create a variable in MainActivity:

 @InjectPresenter lateinit var mPresenter: MainPresenter 


The Moxy plugin will link the View to the fragment and perform other necessary actions.

Create a layout screen with a list and a floating button. Activity_main.xml file:

 <?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> 


To implement the flying button, I used the FloatingActionButton library. Google has already added FAB to the support library, so you can use their solution.

Let us indicate to our Activity which layout it should show:

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


Next, we need to link the FAB and the list so that when scrolling up the list, the button disappears:

 fabButon.attachToRecyclerView(rvNotesList) 


We do not need to write the findViewById order, we just need to register one line in the import block:

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


As you can see, the last package matches the name of our xml file. The IDE automatically initializes the properties of our View and their names are the same as the ID we specified in the markup.

Let's implement the loading of notes from the database. Notes need to be loaded only once and used later. With this we will be helped by the onFirstViewAttach method of the MvpPresenter class, which is called once the first time the View is bound to the presenter. Further, no matter how much we twist and turn our Activity, the data will be cached in a presenter.

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


Create an adapter for our list:

Adapter code
 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 } } } 



In the adapter, we use the formatDate method. It is used to format the date in the string:

 @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) } 


This method is in the DateUtils.kt file and we can use it as a regular static method. The difference from the static method here is that the method does not belong to the class, but to the package, and we do not need to write the class name before the method name. In the annotation we indicate the name of the class through which we will access the method from Java. For example, in Java, the melon method will be called like this:

 String formattedDate = DateUtils.formatDate(date); 


In the onNotesLoaded method of our Activity we show our notes:

 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 } } 


If there are no notes, then we display the “No Notes” message in TextView.

As far as I know, there is no “official” OnItemClickListener for handling clicks on RecycleView items. Therefore, we will use our solution:

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) } } 



In the onCreate method of our Activity, we write:

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


The with function allows you not to write the name of the variable each time, but only to call methods on the object that we passed to it. Please note that to get an Activity, I used not just this , but this @ MainActivity . This is due to the fact that when using this in the with block, the object that we passed to the with function is returned. With the usual click on the item, we turn to Activity, where we can view the text of our note. With a long press, a context menu appears. If you noticed, before the closing bracket I did not write the word return . This is not an error, but a feature of the Kotlin language.

Here is what happens when you click on the menu item in the presenter:

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


We have not yet created the NoteActivity class, so the compiler will generate an error. To solve this problem, you can create a NoteActivity class or comment out the code inside the openNote method altogether. The NoteActivity :: class.java entry is similar to NoteActivity.class in Java. Also note that we refer to the list not through the get (position) method, but through square brackets, like a regular array.

When using the MVP library in your application, we need to get used to the fact that all actions from the View, such as the show / close dialog and others, must pass through the presenter. Initially, this is not very familiar and inconvenient, but there is much more benefit from this, since we can be sure that our dialog box will not disappear anywhere when recreating the Activity.

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


I will not show the context menu code, delete and display information about the note because the article is very large. But I think you get the general sense. It should also be noted that the presenter's hideNoteContextDialog method should be invoked even when the dialog is closed via the back button or when you click on an area outside the dialog boundaries.

When you click on FAB, a new note should be created:

 fabButton.setOnClickListener { mPresenter.openNewNote(this) } 


To create a new note, we call the openNewNote function in the presenter:

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


The openNewNote method uses the openNote we created earlier, to which we pass the Context and the position of the note in the list.


We implement a search for notes





Let's add a search for notes. Create a main.xml file in the res / menu folder:

 <?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> 


In the MainActivity we write:

 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 } } 


When changing the text in the search field, we pass a string from the field to the presenter, and then show the results in the list. In fact, SearchView has no onQueryChange method, it was added by the KAndroid library.

Implement a search in the presenter:

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


Notice how beautiful, in one line, we implemented a search on the list using the filter and lambda methods. In Java, the same functionality would take 6-7 lines. It remains to display the search results:

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



Implement note sorting



And the last step in creating the main screen is sorting notes. Add the following lines to 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> 


Now we need to handle clicking on the menu items:

 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) } 


The when clause is a more functional counterpart to switch-case in Java. Sort code in MainPresenter:
 /** *   */ fun sortNotesBy(sortMethod: SortNotesBy) { mNotesList.sortWith(sortMethod) viewState.updateView() } 



Screen with content notes



Now we need to create a screen with the contents of the notes. Here the user can view / edit the title and text of the note, save or delete it, as well as view information about the note.


Create a NoteView and NotePresenter





The screen contains only three views:

-Headline
-Date of last change
-Text notes

And here is the markup itself:

 <?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> 


At the beginning of the article, I briefly mentioned Anko. The library allows you to significantly reduce the code without losing readability. So, for example, our markup would look when using 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) } } } } 


But let's not get distracted and start writing the code. First we need to create a View:

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


Implement NoteView in NoteActivity:

 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) } } 


In onCreate, we retrieve the id of the note so that the presenter gets the note from the database and passes the data to the View. Create a presenter:

 @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) } } 


Do not forget to add the following line to the AppComponent class:

 fun inject(notePresenter: NotePresenter) 


Show our note:

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



Implement saving notes



To save a note, we need to select the corresponding item in the menu. Create a res / menu / note.xml file :

 <?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) } 


Again, I did not give the code to delete and display information about the note. When viewing the source code, you may notice that in addition to the note identifier, I passed the position of the note in the list to NoteActivity. This is necessary so that when you delete a note on the note viewing screen, it is also removed from the list. To implement this functionality, I used EventBus. And again, I did not give the code.

That's all: notes are added, edited and deleted. We can also search and sort notes. Be sure to look at the full source code, the link to which I gave at the end of the article, to better understand how everything works.


Thanks



Of course, we must not forget about the people who helped me in writing this article. I would like to thank Habrayuser Yuri Shmakov (@senneco) for helping with his Moxy library and for helping with other issues. Also, I want to say thank you to JetBrains employee Roman Belov (@belovrv) for reviewing the article and for providing the code on Anko.

UPD: I also wanted to say thanks to Sirikid for the EPIC COMMIT, thanks to which I redid an impressive part of the code using the features of Kotlin.


Conclusion



I hope this article was able to convince you that writing applications to Kotlin is not difficult, and maybe even easier than in Java. Of course, there may be bugs that JetBrains staff members quickly fix. If you have any questions, you can ask them directly to the developers on the Slack channel . You can also read Kotlin development articles here .

Project Source Code: Notelin .

Source: https://habr.com/ru/post/275255/


All Articles