Peralta, Walter V.
CC17 CITCS 3B-2
XML files
[Link]
<?xml version="1.0" encoding="utf-8"?>
<[Link]
xmlns:android="[Link]
xmlns:app="[Link]
xmlns:tools="[Link]
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<[Link]
android:id="@+id/recyclerview"
android:layout_width="0dp"
android:layout_height="0dp"
tools:listitem="@layout/recyclerview_item"
android:padding="@dimen/big_padding"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<[Link]
android:id="@+id/fab"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/add_word"
android:src="@drawable/ic_add_black_24dp"/>
</[Link]>
activity_new_word.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="[Link]
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit_word"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/min_height"
android:fontFamily="sans-serif-light"
android:hint="@string/hint_word"
android:inputType="textAutoComplete"
android:layout_margin="@dimen/big_padding"
android:textSize="18sp" />
<Button
android:id="@+id/button_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:text="@string/button_save"
android:layout_margin="@dimen/big_padding"
android:textColor="@color/buttonLabel" />
</LinearLayout>
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<[Link]
xmlns:android="[Link]
xmlns:app="[Link]
xmlns:tools="[Link]
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="[Link]"
tools:showIn="@layout/activity_main">
<[Link]
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/recyclerview_item"/>
</[Link]>
recyclerviewer_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="[Link]
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView"
style="@style/word_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/holo_orange_light" />
</LinearLayout>
[Link]
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="buttonLabel">#FFFFFF</color>
</resources>
[Link]
<resources>
<dimen name="small_padding">8dp</dimen>
<dimen name="big_padding">16dp</dimen>
<dimen name="min_height">48dp</dimen>
</resources>
[Link]
<resources>
<string name="app_name">RoomWordsSample</string>
<string name="action_settings">Settings</string>
<string name="hint_word">Word…</string>
<string name="button_save">Save</string>
<string name="empty_not_saved">Word not saved because it is empty.</string>
<string name="add_word">Add word</string>
</resources>
[Link]
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="[Link]">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- The default font for RecyclerView items is too small.
The margin is a simple delimiter between the words. -->
<style name="word_title">
<item name="android:layout_marginBottom">8dp</item>
<item name="android:paddingLeft">8dp</item>
<item name="android:background">@android:color/holo_orange_light</item>
<item
name="android:textAppearance">@android:style/[Link]</item>
</style>
</resources>
Kotlin codes
[Link]
package [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
class MainActivity : AppCompatActivity() {
private val newWordActivityRequestCode = 1
private val wordViewModel: WordViewModel by viewModels {
WordViewModelFactory((application as WordsApplication).repository)
}
override fun onCreate(savedInstanceState: Bundle?) {
[Link](savedInstanceState)
setContentView([Link].activity_main)
val recyclerView = findViewById<RecyclerView>([Link])
val adapter = WordListAdapter()
[Link] = adapter
[Link] = LinearLayoutManager(this)
[Link](owner = this) { words ->
[Link] { [Link](it) }
}
val fab = findViewById<FloatingActionButton>([Link])
[Link] {
val intent = Intent(this@MainActivity, NewWordActivity::[Link])
startActivityForResult(intent, newWordActivityRequestCode)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intentData:
Intent?) {
[Link](requestCode, resultCode, intentData)
if (requestCode == newWordActivityRequestCode && resultCode ==
Activity.RESULT_OK) {
intentData?.getStringExtra(NewWordActivity.EXTRA_REPLY)?.let { reply ->
val word = Word(reply)
[Link](word)
}
} else {
[Link](
applicationContext,
[Link].empty_not_saved,
Toast.LENGTH_LONG
).show()
}
}
}
[Link]
package [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
class NewWordActivity : AppCompatActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
[Link](savedInstanceState)
setContentView([Link].activity_new_word)
val editWordView = findViewById<EditText>([Link].edit_word)
val button = findViewById<Button>([Link].button_save)
[Link] {
val replyIntent = Intent()
if ([Link]([Link])) {
setResult(Activity.RESULT_CANCELED, replyIntent)
} else {
val word = [Link]()
[Link](EXTRA_REPLY, word)
setResult(Activity.RESULT_OK, replyIntent)
}
finish()
}
}
companion object {
const val EXTRA_REPLY = "[Link]"
}
}
[Link]
package [Link]
import [Link]
import [Link]
import [Link]
@Entity(tableName = "word_table")
data class Word(@PrimaryKey @ColumnInfo(name = "word") val word: String)
[Link]
package [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
@Dao
interface WordDao {
// The flow always holds/caches latest version of data. Notifies its observers
when the
// data has changed.
@Query("SELECT * FROM word_table ORDER BY word ASC")
fun getAlphabetizedWords(): Flow<List<Word>>
@Insert(onConflict = [Link])
suspend fun insert(word: Word)
@Query("DELETE FROM word_table")
suspend fun deleteAll()
}
[Link]
package [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
class WordListAdapter : ListAdapter<Word, WordViewHolder>(WORDS_COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder
{
return [Link](parent)
}
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = getItem(position)
[Link]([Link])
}
class WordViewHolder(itemView: View) : [Link](itemView) {
private val wordItemView: TextView = [Link]([Link])
fun bind(text: String?) {
[Link] = text
}
companion object {
fun create(parent: ViewGroup): WordViewHolder {
val view: View = [Link]([Link])
.inflate([Link].recyclerview_item, parent, false)
return WordViewHolder(view)
}
}
}
companion object {
private val WORDS_COMPARATOR = object : [Link]<Word>() {
override fun areItemsTheSame(oldItem: Word, newItem: Word): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Word, newItem: Word): Boolean {
return [Link] == [Link]
}
}
}
}
[Link]
package [Link]
import [Link]
import [Link]
class WordRepository(private val wordDao: WordDao) {
val allWords: Flow<List<Word>> = [Link]()
@Suppress("RedundantSuspendModifier")
@WorkerThread
suspend fun insert(word: Word) {
[Link](word)
}
}
[Link]
package [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
@Database(entities = [Word::class], version = 1)
abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
companion object {
@Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(
context: Context,
scope: CoroutineScope
): WordRoomDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = [Link](
[Link],
WordRoomDatabase::[Link],
"word_database"
)
.fallbackToDestructiveMigration()
.addCallback(WordDatabaseCallback(scope))
.build()
INSTANCE = instance
instance
}
}
private class WordDatabaseCallback(
private val scope: CoroutineScope
) : [Link]() {
override fun onCreate(db: SupportSQLiteDatabase) {
[Link](db)
// If you want to keep the data through app restarts,
// comment out the following line.
INSTANCE?.let { database ->
[Link]([Link]) {
populateDatabase([Link]())
}
}
}
}
suspend fun populateDatabase(wordDao: WordDao) {
// Start the app with a clean database every time.
// Not needed if you only populate on creation.
[Link]()
var word = Word("Hello")
[Link](word)
word = Word("World!")
[Link](word)
}
}
}
[Link]
package [Link]
import [Link]
import [Link]
import [Link]
class WordsApplication : Application() {
val applicationScope = CoroutineScope(SupervisorJob())
val database by lazy { [Link](this, applicationScope) }
val repository by lazy { WordRepository([Link]()) }
}
[Link]
package [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
class WordViewModel(private val repository: WordRepository) : ViewModel() {
val allWords: LiveData<List<Word>> = [Link]()
fun insert(word: Word) = [Link] {
[Link](word)
}
}
class WordViewModelFactory(private val repository: WordRepository) :
[Link] {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if ([Link](WordViewModel::[Link])) {
@Suppress("UNCHECKED_CAST")
return WordViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
[Link]
package [Link]
import [Link]
import [Link]
import [Link]
import [Link].AndroidJUnit4
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
@RunWith(AndroidJUnit4::class)
class WordDaoTest {
private lateinit var wordDao: WordDao
private lateinit var db: WordRoomDatabase
@Before
fun createDb() {
val context: Context = [Link]()
// Using an in-memory database because the information stored here disappears
when the
// process is killed.
db = [Link](context, WordRoomDatabase::[Link])
// Allowing main thread queries, just for testing.
.allowMainThreadQueries()
.build()
wordDao = [Link]()
}
@After
@Throws(IOException::class)
fun closeDb() {
[Link]()
}
@Test
@Throws(Exception::class)
fun insertAndGetWord() = runBlocking {
val word = Word("word")
[Link](word)
val allWords = [Link]().first()
assertEquals(allWords[0].word, [Link])
}
@Test
@Throws(Exception::class)
fun getAllWords() = runBlocking {
val word = Word("aaa")
[Link](word)
val word2 = Word("bbb")
[Link](word2)
val allWords = [Link]().first()
assertEquals(allWords[0].word, [Link])
assertEquals(allWords[1].word, [Link])
}
@Test
@Throws(Exception::class)
fun deleteAll() = runBlocking {
val word = Word("word")
[Link](word)
val word2 = Word("word2")
[Link](word2)
[Link]()
val allWords = [Link]().first()
assertTrue([Link]())
}
}
Output