Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ jobs:
sudo gcloud firebase test android run \
--app todoapp/app/build/outputs/apk/mock/debug/app-mock-debug.apk \
--test todoapp/app/build/outputs/apk/androidTest/mock/debug/app-mock-debug-androidTest.apk \
--device-ids=walleye \
--results-bucket cloud-test-${GOOGLE_PROJECT_ID}-blueprints \
--results-dir=${BUILD_DIR}
- run:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,20 @@ import com.example.android.architecture.blueprints.todoapp.tasks.TasksViewModel
* actually necessary in this case, as the product ID can be passed in a public method.
*/
class ViewModelFactory private constructor(
private val application: Application,
private val tasksRepository: TasksRepository
) : ViewModelProvider.NewInstanceFactory() {

override fun <T : ViewModel> create(modelClass: Class<T>) =
with(modelClass) {
when {
isAssignableFrom(StatisticsViewModel::class.java) ->
StatisticsViewModel(application, tasksRepository)
StatisticsViewModel(tasksRepository)
isAssignableFrom(TaskDetailViewModel::class.java) ->
TaskDetailViewModel(tasksRepository)
isAssignableFrom(AddEditTaskViewModel::class.java) ->
AddEditTaskViewModel(application, tasksRepository)
AddEditTaskViewModel(tasksRepository)
isAssignableFrom(TasksViewModel::class.java) ->
TasksViewModel(application, tasksRepository)
TasksViewModel(tasksRepository)
else ->
throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
}
Expand All @@ -61,7 +60,7 @@ class ViewModelFactory private constructor(

fun getInstance(application: Application) =
INSTANCE ?: synchronized(ViewModelFactory::class.java) {
INSTANCE ?: ViewModelFactory(application,
INSTANCE ?: ViewModelFactory(
Injection.provideTasksRepository(application.applicationContext))
.also { INSTANCE = it }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@

package com.example.android.architecture.blueprints.todoapp.addedittask

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.example.android.architecture.blueprints.todoapp.Event
import com.example.android.architecture.blueprints.todoapp.R
import com.example.android.architecture.blueprints.todoapp.data.Task
Expand All @@ -36,9 +35,8 @@ import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepo
* how to deal with more complex scenarios.
*/
class AddEditTaskViewModel(
context: Application,
private val tasksRepository: TasksRepository
) : AndroidViewModel(context), TasksDataSource.GetTaskCallback {
) : ViewModel(), TasksDataSource.GetTaskCallback {

// Two-way databinding, exposing MutableLiveData
val title = MutableLiveData<String>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,9 @@

package com.example.android.architecture.blueprints.todoapp.statistics

import android.app.Application
import android.content.Context
import androidx.databinding.Bindable
import androidx.databinding.ObservableBoolean
import androidx.databinding.ObservableField
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.android.architecture.blueprints.todoapp.R
import androidx.lifecycle.ViewModel
import com.example.android.architecture.blueprints.todoapp.data.Task
import com.example.android.architecture.blueprints.todoapp.data.source.TasksDataSource
import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository
Expand All @@ -38,8 +32,9 @@ import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepo
* whereas the [Bindable] getters allow us to add some logic to it. This is
* preferable to having logic in the XML layout.
*/
class StatisticsViewModel(context: Application, private val tasksRepository: TasksRepository) :
AndroidViewModel(context) {
class StatisticsViewModel(
private val tasksRepository: TasksRepository
) : ViewModel() {

private val _dataLoading = MutableLiveData<Boolean>()
val dataLoading: LiveData<Boolean>
Expand All @@ -54,12 +49,12 @@ class StatisticsViewModel(context: Application, private val tasksRepository: Tas
val empty: LiveData<Boolean>
get() = _empty

private val _numberOfActiveTasks = MutableLiveData<String>()
val numberOfActiveTasks: LiveData<String>
private val _numberOfActiveTasks = MutableLiveData<Int>()
val numberOfActiveTasks: LiveData<Int>
get() = _numberOfActiveTasks

private val _numberOfCompletedTasks = MutableLiveData<String>()
val numberOfCompletedTasks: LiveData<String>
private val _numberOfCompletedTasks = MutableLiveData<Int>()
val numberOfCompletedTasks: LiveData<Int>
get() = _numberOfCompletedTasks

private val _empty = MutableLiveData<Boolean>()
Expand All @@ -68,13 +63,6 @@ class StatisticsViewModel(context: Application, private val tasksRepository: Tas

private var completedTasks = 0

private val context: Context


init {
this.context = context
}

fun start() {
loadStatistics()
}
Expand Down Expand Up @@ -118,11 +106,9 @@ class StatisticsViewModel(context: Application, private val tasksRepository: Tas
}

private fun updateDataBindingObservables() {
_numberOfCompletedTasks.value =
context.getString(R.string.statistics_completed_tasks, completedTasks)
_numberOfCompletedTasks.value = completedTasks

_numberOfActiveTasks.value =
context.getString(R.string.statistics_active_tasks, activeTasks)
_numberOfActiveTasks.value = activeTasks

_empty.value = activeTasks + completedTasks == 0
_dataLoading.value = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,12 @@
*/
package com.example.android.architecture.blueprints.todoapp.tasks

import android.app.Application
import android.content.Context
import android.graphics.drawable.Drawable
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import com.example.android.architecture.blueprints.todoapp.Event
import com.example.android.architecture.blueprints.todoapp.R
import com.example.android.architecture.blueprints.todoapp.addedittask.AddEditTaskActivity
Expand All @@ -45,9 +41,8 @@ import java.util.ArrayList
* getter method.
*/
class TasksViewModel(
context: Application,
private val tasksRepository: TasksRepository
) : AndroidViewModel(context) {
) : ViewModel() {

private val _items = MutableLiveData<List<Task>>().apply { value = emptyList() }
val items: LiveData<List<Task>>
Expand All @@ -57,16 +52,16 @@ class TasksViewModel(
val dataLoading: LiveData<Boolean>
get() = _dataLoading

private val _currentFilteringLabel = MutableLiveData<String>()
val currentFilteringLabel: LiveData<String>
private val _currentFilteringLabel = MutableLiveData<Int>()
val currentFilteringLabel: LiveData<Int>
get() = _currentFilteringLabel

private val _noTasksLabel = MutableLiveData<String>()
val noTasksLabel: LiveData<String>
private val _noTasksLabel = MutableLiveData<Int>()
val noTasksLabel: LiveData<Int>
get() = _noTasksLabel

private val _noTaskIconRes = MutableLiveData<Drawable>()
val noTaskIconRes: LiveData<Drawable>
private val _noTaskIconRes = MutableLiveData<Int>()
val noTaskIconRes: LiveData<Int>
get() = _noTaskIconRes

private val _tasksAddViewVisible = MutableLiveData<Boolean>()
Expand All @@ -90,9 +85,6 @@ class TasksViewModel(
val newTaskEvent: LiveData<Event<Unit>>
get() = _newTaskEvent

// To prevent leaks, this must be an Application Context.
private val context: Context = context.applicationContext

// This LiveData depends on another so we can use a transformation.
val empty: LiveData<Boolean> = Transformations.map(_items) {
it.isEmpty()
Expand Down Expand Up @@ -140,9 +132,9 @@ class TasksViewModel(

private fun setFilter(@StringRes filteringLabelString: Int, @StringRes noTasksLabelString: Int,
@DrawableRes noTaskIconDrawable: Int, tasksAddVisible: Boolean) {
_currentFilteringLabel.value = context.getString(filteringLabelString)
_noTasksLabel.value = context.getString(noTasksLabelString)
_noTaskIconRes.value = ContextCompat.getDrawable(context, noTaskIconDrawable)
_currentFilteringLabel.value = filteringLabelString
_noTasksLabel.value = noTasksLabelString
_noTaskIconRes.value = noTaskIconDrawable
_tasksAddViewVisible.value = tasksAddVisible
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,18 @@ import com.google.android.material.snackbar.Snackbar
* Transforms static java function Snackbar.make() to an extension function on View.
*/
fun View.showSnackbar(snackbarText: String, timeLength: Int) {
Snackbar.make(this, snackbarText, timeLength).show()
Snackbar.make(this, snackbarText, timeLength).run {
addCallback(object: Snackbar.Callback() {
override fun onShown(sb: Snackbar?) {
EspressoIdlingResource.increment()
}

override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
EspressoIdlingResource.decrement()
}
})
show()
}
}

/**
Expand Down Expand Up @@ -62,4 +73,4 @@ fun View.setupSnackbar(
fun ScrollChildSwipeRefreshLayout.setSwipeRefreshLayoutOnRefreshListener(
viewModel: TasksViewModel) {
setOnRefreshListener { viewModel.loadTasks(true) }
}
}
2 changes: 2 additions & 0 deletions todoapp/app/src/main/res/layout/addtask_frag.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@
android:layout_height="wrap_content"
android:hint="@string/title_hint"
android:maxLines="1"
android:imeOptions="flagNoExtractUi"
android:text="@={viewmodel.title}"
android:textAppearance="@style/TextAppearance.AppCompat.Title"/>

<EditText
android:id="@+id/add_task_description"
android:layout_width="match_parent"
android:layout_height="350dp"
android:imeOptions="flagNoExtractUi"
android:gravity="top"
android:hint="@string/description_hint"
android:text="@={viewmodel.description}"/>
Expand Down
6 changes: 3 additions & 3 deletions todoapp/app/src/main/res/layout/statistics_frag.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,17 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{stats.numberOfActiveTasks}"
android:text="@{@string/statistics_active_tasks(stats.numberOfActiveTasks)}"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="@{stats.empty ? View.GONE : View.VISIBLE}" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{stats.numberOfCompletedTasks}"
android:text="@{@string/statistics_completed_tasks(stats.numberOfCompletedTasks)}"
android:textAppearance="?android:attr/textAppearanceMedium"
android:visibility="@{stats.empty ? View.GONE : View.VISIBLE}" />

</LinearLayout>
</LinearLayout>
</layout>
</layout>
8 changes: 5 additions & 3 deletions todoapp/app/src/main/res/layout/tasks_frag.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

<import type="android.view.View" />

<import type="androidx.core.content.ContextCompat" />

<variable
name="viewmodel"
type="com.example.android.architecture.blueprints.todoapp.tasks.TasksViewModel" />
Expand Down Expand Up @@ -57,7 +59,7 @@
android:layout_marginRight="@dimen/list_item_padding"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:text="@{viewmodel.currentFilteringLabel}" />
android:text="@{context.getString(viewmodel.currentFilteringLabel)}" />

<ListView
android:id="@+id/tasks_list"
Expand All @@ -79,14 +81,14 @@
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:src="@{viewmodel.noTaskIconRes}" />
android:src="@{ContextCompat.getDrawable(context, viewmodel.noTaskIconRes)}" />

<TextView
android:id="@+id/noTasksMain"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@{viewmodel.noTasksLabel}"
android:text="@{context.getString(viewmodel.noTasksLabel)}"
android:layout_marginBottom="@dimen/list_item_padding"/>

<TextView
Expand Down
2 changes: 1 addition & 1 deletion todoapp/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
-->

<resources>
<string name="app_name">TO-DO-MVVM-Lifecycles23232323f</string>
<string name="app_name">TO-DO-MVVM-Lifecycles</string>
<string name="add_task">New TO-DO</string>
<string name="edit_task">Edit TO-DO</string>
<string name="task_marked_complete">Task marked complete</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class AddEditTaskViewModelTest {
MockitoAnnotations.initMocks(this)

// Get a reference to the class under test
addEditTaskViewModel = AddEditTaskViewModel(mock<Application>(), tasksRepository)
addEditTaskViewModel = AddEditTaskViewModel(tasksRepository)
}

@Test fun saveNewTaskToRepository_showsSuccessMessageUi() {
Expand All @@ -78,7 +78,7 @@ class AddEditTaskViewModelTest {
val testTask = Task("TITLE", "DESCRIPTION", "1")

// Get a reference to the class under test
addEditTaskViewModel = AddEditTaskViewModel(mock<Application>(), tasksRepository).apply {
addEditTaskViewModel = AddEditTaskViewModel(tasksRepository).apply {
// When the ViewModel is asked to populate an existing task
start(testTask.id)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package com.example.android.architecture.blueprints.todoapp.statistics


import android.app.Application
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.example.android.architecture.blueprints.todoapp.LiveDataTestUtil
import com.example.android.architecture.blueprints.todoapp.data.Task
Expand All @@ -33,7 +32,6 @@ import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations

Expand All @@ -56,7 +54,7 @@ class StatisticsViewModelTest {
MockitoAnnotations.initMocks(this)

// Get a reference to the class under test
statisticsViewModel = StatisticsViewModel(mock(Application::class.java), tasksRepository)
statisticsViewModel = StatisticsViewModel(tasksRepository)

// We initialise the tasks to 3, with one active and two completed
val task1 = Task("Title1", "Description1")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class TasksViewModelTest {
setupContext()

// Get a reference to the class under test
tasksViewModel = TasksViewModel(context, tasksRepository)
tasksViewModel = TasksViewModel(tasksRepository)

// We initialise the tasks to 3, with one active and two completed
val task1 = Task("Title1", "Description1")
Expand Down
2 changes: 1 addition & 1 deletion todoapp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
classpath 'com.android.tools.build:gradle:3.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"

// NOTE: Do not place your application dependencies here; they belong
Expand Down