Skip to content
This repository was archived by the owner on Aug 22, 2024. It is now read-only.

Commit 45d2b94

Browse files
committed
[ADDED] [#8] ViewModel factory and viewmodel for the layout info activity.
[REFACTO] Activity to use the viewmodel.
1 parent 22bf98f commit 45d2b94

File tree

5 files changed

+181
-11
lines changed

5 files changed

+181
-11
lines changed

app/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ dependencies {
102102
exclude group: 'com.android.support', module: 'support-annotations'
103103
})
104104
androidTestImplementation 'com.android.support.test:runner:1.0.2'
105+
testImplementation "android.arch.core:core-testing:$rootProject.archComponentVersion"
105106
}
106107

107108
// ADD THIS AT THE BOTTOM
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright (c) 2018 Hossain Khan
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.hossainkhan.android.demo.layoutpreview
18+
19+
import android.arch.lifecycle.LiveData
20+
import android.arch.lifecycle.MutableLiveData
21+
import android.arch.lifecycle.ViewModel
22+
import android.support.annotation.LayoutRes
23+
import com.hossainkhan.android.demo.data.AppDataStore
24+
import com.hossainkhan.android.demo.data.LayoutInformation
25+
import timber.log.Timber
26+
27+
/**
28+
* ViewModel for containing layout information.
29+
*/
30+
class LayoutInfoViewModel(private val appDataStore: AppDataStore) : ViewModel() {
31+
private val layoutInfoLiveData: MutableLiveData<LayoutInformation> = MutableLiveData()
32+
33+
val layoutInformation: LiveData<LayoutInformation>
34+
get() = layoutInfoLiveData
35+
36+
/**
37+
* Provides layout URL for the currently initialized layout resource id.
38+
* @see init
39+
*/
40+
val layoutUrl: String
41+
get() = appDataStore.layoutStore.getLayoutUrl(layoutInfoLiveData.value!!.layoutResourceId)
42+
43+
/**
44+
* Indicates if the current screen is being shown for the first time.
45+
*/
46+
val isFirstTime: Boolean
47+
get() = appDataStore.shouldshowLayoutInformation(layoutInfoLiveData.value!!.layoutResourceId)
48+
49+
50+
fun init(@LayoutRes layoutResourceId: Int) {
51+
val layoutInfo = appDataStore.layoutStore.layoutsInfos[layoutResourceId]!!
52+
Timber.d("Loading layout: %s", layoutInfoLiveData)
53+
54+
layoutInfoLiveData.value = layoutInfo
55+
}
56+
}

app/src/main/java/com/hossainkhan/android/demo/layoutpreview/LayoutPreviewBaseActivity.kt

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package com.hossainkhan.android.demo.layoutpreview
1818

19+
import android.arch.lifecycle.Observer
20+
import android.arch.lifecycle.ViewModelProvider
1921
import android.content.Context
2022
import android.content.Intent
2123
import android.net.Uri
@@ -28,8 +30,8 @@ import android.view.Menu
2830
import android.view.MenuItem
2931
import com.andrognito.flashbar.Flashbar
3032
import com.hossainkhan.android.demo.R
31-
import com.hossainkhan.android.demo.data.AppDataStore
3233
import com.hossainkhan.android.demo.data.LayoutInformation
34+
import com.hossainkhan.android.demo.viewmodel.LayoutPreviewViewModelFactory
3335
import dagger.android.AndroidInjection
3436
import timber.log.Timber
3537
import javax.inject.Inject
@@ -62,25 +64,36 @@ open class LayoutPreviewBaseActivity : AppCompatActivity() {
6264
}
6365

6466
@Inject
65-
internal lateinit var appDataStore: AppDataStore
67+
internal lateinit var viewModelFactory: LayoutPreviewViewModelFactory
68+
69+
private lateinit var viewModel: LayoutInfoViewModel
6670

67-
internal lateinit var layoutInformation: LayoutInformation
6871
internal var flashbar: Flashbar? = null
6972

7073
override fun onCreate(savedInstanceState: Bundle?) {
7174
AndroidInjection.inject(this)
7275
super.onCreate(savedInstanceState)
7376

7477
val layoutResourceId = intent.getIntExtra(BUNDLE_KEY_LAYOUT_RESID, -1)
75-
layoutInformation = appDataStore.layoutStore.layoutsInfos[layoutResourceId]!!
76-
Timber.d("Loading layout: %s", layoutInformation)
7778

7879
setContentView(layoutResourceId)
7980

80-
supportActionBar?.title = layoutInformation.title
81+
viewModel = ViewModelProvider(this, viewModelFactory)
82+
.get(LayoutInfoViewModel::class.java)
83+
viewModel.init(layoutResourceId)
84+
8185
supportActionBar?.setDisplayHomeAsUpEnabled(true)
8286

83-
showLayoutInfo(layoutInformation)
87+
viewModel.layoutInformation.observe(this,
88+
Observer { layoutInfo ->
89+
Timber.d("Received layout info from LiveData: %s", layoutInfo)
90+
updateActionBar(layoutInfo!!)
91+
showLayoutInfo(layoutInfo)
92+
})
93+
}
94+
95+
private fun updateActionBar(layoutInformation: LayoutInformation) {
96+
supportActionBar?.title = layoutInformation.title
8497
}
8598

8699
/**
@@ -115,7 +128,7 @@ open class LayoutPreviewBaseActivity : AppCompatActivity() {
115128

116129
Timber.d("Flash bar showing: %s", flashbar?.isShown())
117130
if (flashbar?.isShown() == false) {
118-
if (fromUser || appDataStore.shouldshowLayoutInformation(layoutInformation.layoutResourceId)) {
131+
if (fromUser || viewModel.isFirstTime) {
119132
flashbar?.show()
120133
}
121134
} else {
@@ -127,12 +140,11 @@ open class LayoutPreviewBaseActivity : AppCompatActivity() {
127140
* Loads currently running layout from Github into chrome web view.
128141
*/
129142
fun loadLayoutUrl() {
130-
val layoutUrl = appDataStore.layoutStore.getLayoutUrl(layoutInformation.layoutResourceId)
131143
val builder = CustomTabsIntent.Builder()
132144
builder.setShowTitle(false)
133145
.addDefaultShareMenuItem()
134146
val customTabsIntent = builder.build()
135-
customTabsIntent.launchUrl(this, Uri.parse(layoutUrl))
147+
customTabsIntent.launchUrl(this, Uri.parse(viewModel.layoutUrl))
136148
}
137149

138150

@@ -148,7 +160,7 @@ open class LayoutPreviewBaseActivity : AppCompatActivity() {
148160
override fun onOptionsItemSelected(item: MenuItem): Boolean {
149161
return when (item.itemId) {
150162
R.id.show_layout_info_menu_item -> {
151-
showLayoutInfo(layoutInformation, true)
163+
showLayoutInfo(viewModel.layoutInformation.value!!, true)
152164
true
153165
}
154166
android.R.id.home -> {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright (c) 2018 Hossain Khan
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.hossainkhan.android.demo.viewmodel
18+
19+
import android.arch.lifecycle.ViewModel
20+
import android.arch.lifecycle.ViewModelProvider
21+
import com.hossainkhan.android.demo.data.AppDataStore
22+
import com.hossainkhan.android.demo.layoutpreview.LayoutInfoViewModel
23+
import javax.inject.Inject
24+
25+
class LayoutPreviewViewModelFactory @Inject constructor(
26+
private val dataStore: AppDataStore
27+
28+
) : ViewModelProvider.Factory {
29+
@Suppress("UNCHECKED_CAST")
30+
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
31+
return when {
32+
modelClass.isAssignableFrom(LayoutInfoViewModel::class.java) ->
33+
LayoutInfoViewModel(dataStore) as T
34+
else -> throw IllegalArgumentException("Unknown ViewModel class")
35+
}
36+
}
37+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2018 Hossain Khan
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.hossainkhan.android.demo.layoutpreview
18+
19+
import android.content.SharedPreferences
20+
import android.content.res.Resources
21+
import com.hossainkhan.android.demo.data.AppDataStore
22+
import com.hossainkhan.android.demo.data.LayoutDataStore
23+
import org.junit.Test
24+
25+
import org.junit.Assert.*
26+
import org.junit.Before
27+
import org.mockito.Mockito.mock
28+
import android.arch.core.executor.testing.InstantTaskExecutorRule
29+
import org.junit.rules.TestRule
30+
import org.junit.Rule
31+
32+
33+
class LayoutInfoViewModelTest {
34+
/**
35+
* Uses rule to test LiveData
36+
*
37+
* References:
38+
* - https://medium.com/pxhouse/unit-testing-with-mutablelivedata-22b3283a7819
39+
* - https://stackoverflow.com/questions/29945087/kotlin-and-new-activitytestrule-the-rule-must-be-public
40+
*/
41+
@get:Rule
42+
val rule: TestRule = InstantTaskExecutorRule()
43+
44+
private val resources: Resources = mock(Resources::class.java)
45+
private val preferences: SharedPreferences = mock(SharedPreferences::class.java)
46+
private val layoutStore = LayoutDataStore(resources)
47+
48+
private val mockLayoutId = layoutStore.supportedLayoutInfos.first().layoutResourceId
49+
50+
lateinit var sut: LayoutInfoViewModel
51+
52+
@Before
53+
fun setup() {
54+
sut = LayoutInfoViewModel(AppDataStore(preferences, layoutStore))
55+
}
56+
57+
@Test
58+
fun getLayoutInformation_givenLayoutId_providesLayoutInformationViaLiveData() {
59+
sut.init(mockLayoutId)
60+
61+
assertEquals(layoutStore.layoutsInfos[mockLayoutId], sut.layoutInformation.value)
62+
}
63+
64+
}

0 commit comments

Comments
 (0)