- Read Data
- Add Data
- Delete All Data
- Delete One Item Data
- Edit Data
- Swipe Actions
The purpose of Architecture Components is to provide guidance on app architecture, with libraries for common tasks like lifecycle management and data persistence. That's why to facilitate it I made an example in Kotlin 100%.
In your build.gradle
(Module: app) make the following changes:
On top:
apply plugin: 'kotlin-kapt'
In the dependencies:
// ROOM COMPONENTS
implementation "android.arch.persistence.room:runtime:1.1.1"
kapt "android.arch.persistence.room:compiler:1.1.1"
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
// LIFECYCLE COMPONENTS
implementation "android.arch.lifecycle:extensions:1.1.1"
kapt "android.arch.lifecycle:compiler:1.1.1"
// COROUTINES
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
In the WeatherService (interface), you GET de data, doing the query to the data that you need
@GET("data/2.5/weather?")
fun getCurrentWeatherData(@Query("lat") lat: String, @Query("lon") lon: String, @Query("APPID") app_id: String): Call<WeatherResponse>
¡Not all API's have the same pattern, so keep that in mind!
To make the Word
class meaningful to a Room database, you need to annotate it. Annotations identify how each part of this class relates to an entry in the database. Room uses this information to generate code.
@Entity(tableName = "word_table")
class Word(@PrimaryKey @ColumnInfo(name = "word") val word: String)
The DAO for this example is basic and provides queries for getting all the words, inserting a word, and deleting all the words.
@Dao
interface WordDao {
@Query("SELECT * from word_table ORDER BY word ASC")
fun getAllWords(): LiveData<List<Word>>
@Insert
fun insert(word: Word)
@Query("DELETE FROM word_table")
fun deleteAll()
}
This Room database class must be abstract and extend RoomDatabase
. Usually, you only need one instance of a Room database for the whole app.
@Database(entities = [Word::class], version = 1, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope): WordRoomDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
WordRoomDatabase::class.java,
"word_database"
)
.fallbackToDestructiveMigration()
.addCallback(WordDatabaseCallback(scope))
.build()
INSTANCE = instance
instance
}
}
private class WordDatabaseCallback(
private val scope: CoroutineScope
) : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let {
scope.launch(Dispatchers.IO) {
}
}
}
}
}
}
A Repository
class abstracts access to multiple data sources. It use for manages queries and allows you to use multiple backends. In the most common example, the Repository implements the logic for deciding whether to fetch data from a network or use results cached in a local database.
class WordRepository(private val wordDao: WordDao) {
val allWords: LiveData<List<Word>> = wordDao.getAllWords()
@WorkerThread
suspend fun insert(word: Word) {
wordDao.insert(word)
}
}
A ViewModel
holds your app's UI data in a lifecycle-conscious way that survives configuration changes. Separating your app's UI data from your Activity
and Fragment
classes lets you better follow the single responsibility principle: Your activities and fragments are responsible for drawing data to the screen, while your ViewModel
can take care of holding and processing all the data needed for the UI.
class WordViewModel(application: Application) : AndroidViewModel(application) {
private var parentJob = Job()
private val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.Main
private val scope = CoroutineScope(coroutineContext)
private val repository: WordRepository
val allWords: LiveData<List<Word>>
init {
val wordsDao = WordRoomDatabase.getDatabase(application, scope).wordDao()
repository = WordRepository(wordsDao)
allWords = repository.allWords
}
fun insert(word: Word) = scope.launch(Dispatchers.IO) {
repository.insert(word)
}
override fun onCleared() {
super.onCleared()
parentJob.cancel()
}
}
You are going to display the data in a RecyclerView
, which is a little nicer than just throwing the data in a TextView
and ImageView
.
class WordListAdapter internal constructor(context: Context) : RecyclerView.Adapter<WordListAdapter.WordViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var words = emptyList<Word>() // Cached copy of words
inner class WordViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val wordItemView: TextView = itemView.findViewById(R.id.textView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {
val itemView = inflater.inflate(R.layout.recyclerview_word, parent, false)
return WordViewHolder(itemView)
}
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = words[position]
holder.wordItemView.text = current.word
}
internal fun setWords(words: List<Word>) {
this.words = words
notifyDataSetChanged()
}
override fun getItemCount() = words.size
}