Preview
Last updated
Was this helpful?
Last updated
Was this helpful?
Q: What are we trying to achieve here?
A: We're trying to store & show list of Names.
Let's add permissions first.
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
And time to add dependencies.
apply plugin: 'kotlin-kapt'
dependencies {
...
// UI Component
implementation 'androidx.recyclerview:recyclerview:1.1.0'
// Material design
implementation "com.google.android.material:material:1.1.0"
// LifeStyle Components
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
kapt "androidx.lifecycle:lifecycle-compiler:2.2.0"
// Kotlin Components
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.4"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.4"
// Room Components
implementation "androidx.room:room-runtime:2.2.5"
kapt "androidx.room:room-compiler:2.2.5"
implementation "androidx.room:room-ktx:2.2.5"
...
}
List item data class
data class ListItem (
val title: String,
val subtitle: String
)
Create Adapter for RecyclerView List - which will display list of words.
class MainListAdapter : RecyclerView.Adapter<MainListAdapter.ViewHolder>() {
private var list: List<ListItem> = emptyList()
class ViewHolder(view: View): RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainListAdapter.ViewHolder {
return ViewHolder(
LayoutInflater
.from(parent.context)
.inflate(R.layout.list_main, parent, false)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.title.text = list[position].title
holder.itemView.subtitle.text = list[position].subtitle
holder.itemView.setOnClickListener {
println("Clicked on position $position")
}
println("Recycling view - $position")
}
override fun getItemCount(): Int {
return list.size
}
internal fun setListItems(items: List<ListItem>) {
this.list = items
notifyDataSetChanged()
}
}
Layout file for single row - which is used in above adapter.
ImageView we'll be using in next article.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/imageView"
app:layout_constraintTop_toTopOf="@+id/imageView">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sagar"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<TextView
android:id="@+id/subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Kothari"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Create table. For this article, we're creating name table.
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "names_table")
class Name(
@PrimaryKey(autoGenerate = true) val id: Int? = null,
@ColumnInfo(name = "name") val name: String
)
Data Access Object - An interface - with which we'll be able to access Name table.
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@Dao
interface NameDao {
@Query("SELECT * from names_table ORDER BY name ASC")
fun getAlphabetizedNames(): LiveData<List<Name>>
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(name: Name)
@Query("DELETE FROM names_table")
suspend fun deleteAll()
}
Repository with which we'll be able to insert / delete / update / read.
import androidx.lifecycle.LiveData
class NameRepository(private val nameDao: NameDao) {
val allNames: LiveData<List<Name>> = nameDao.getAlphabetizedNames()
suspend fun insert(name: Name) {
nameDao.insert(name)
}
}
Database connection
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [Name::class], version = 1, exportSchema = false)
public abstract class NameRoomDatabase : RoomDatabase() {
abstract fun nameDao(): NameDao
companion object {
@Volatile
private var INSTANCE: NameRoomDatabase? = null
fun getDatabase(context: Context): NameRoomDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
NameRoomDatabase::class.java,
"name_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
ViewModel for async, easy, quick access to Name table.
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class NameViewModel(application: Application) : AndroidViewModel(application) {
private val repository: NameRepository
val allNames: LiveData<List<Name>>
init {
val namesDao = NameRoomDatabase.getDatabase(application).nameDao()
repository = NameRepository(namesDao)
allNames = repository.allNames
}
fun insert(name: Name) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(name)
}
}
Layout for MainActivity
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="@drawable/ic_add"
app:layout_constraintBottom_toBottomOf="@id/listView"
app:layout_constraintEnd_toEndOf="@id/listView"
app:rippleColor="@color/colorPrimary" />
</androidx.constraintlayout.widget.ConstraintLayout>
Layout for AddItemActivity
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/editTextWord"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:layout_margin="10dp"
android:autofillHints="@string/enter_name"
android:hint="@string/enter_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/saveButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/save_button"
android:layout_margin="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
class MainActivity : AppCompatActivity() {
private lateinit var nameViewModel: NameViewModel
private val newNameActivityRequestCode = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val listAdapter = MainListAdapter()
listView.adapter = listAdapter
nameViewModel = ViewModelProvider(this).get(NameViewModel::class.java)
nameViewModel.allNames.observe(this, Observer { names ->
names?.let { listAdapter.setListItems(it.map { ListItem(it.name, "ID: ${it.id}") }) }
})
fab.setOnClickListener {
startActivityForResult(
Intent(this@MainActivity, AddItemActivity::class.java),
newNameActivityRequestCode)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val text = data?.getStringExtra(AddItemActivity.EXTRA_REPLY)
if (newNameActivityRequestCode == requestCode && resultCode == Activity.RESULT_OK && text != null) {
nameViewModel.insert(Name(null, text!!))
} else {
Toast.makeText(applicationContext, R.string.save_error, Toast.LENGTH_LONG)
}
}
}
class AddItemActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_additem)
saveButton.setOnClickListener {
val replyIntent = Intent()
if (TextUtils.isEmpty(editTextWord.text)) {
setResult(Activity.RESULT_CANCELED, replyIntent)
} else {
replyIntent.putExtra(EXTRA_REPLY, editTextWord.text.toString())
setResult(Activity.RESULT_OK, replyIntent)
}
finish()
}
}
companion object {
const val EXTRA_REPLY = "com.sagar_r_kothari.mycardsholder.AdditemActivity.Reply"
}
}