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

Commit 67b9ce0

Browse files
authored
Merge pull request #38 from amardeshbd/feature/8_use_mvvm_architecture
Feature/8 use mvvm architecture
2 parents 22bf98f + 93d367b commit 67b9ce0

File tree

13 files changed

+271
-45
lines changed

13 files changed

+271
-45
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

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
android:roundIcon="@mipmap/ic_launcher_round"
1111
android:supportsRtl="true"
1212
android:theme="@style/AppTheme">
13-
<activity android:name=".browse.MainActivity">
13+
<activity android:name=".browse.LayoutBrowseActivity">
1414
<intent-filter>
1515
<action android:name="android.intent.action.MAIN" />
1616

@@ -19,10 +19,10 @@
1919
</activity>
2020
<activity
2121
android:name=".layoutpreview.LayoutPreviewBaseActivity"
22-
android:parentActivityName=".browse.MainActivity" />
22+
android:parentActivityName=".browse.LayoutBrowseActivity" />
2323
<activity
2424
android:name=".layoutpreview.LayoutVisibilityGoneActivity"
25-
android:parentActivityName=".browse.MainActivity" />
25+
android:parentActivityName=".browse.LayoutBrowseActivity" />
2626
</application>
2727

2828
</manifest>

app/src/main/java/com/hossainkhan/android/demo/browse/MainActivity.kt renamed to app/src/main/java/com/hossainkhan/android/demo/browse/LayoutBrowseActivity.kt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,48 @@
1616

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

19-
import android.support.v7.app.AppCompatActivity
19+
import android.arch.lifecycle.ViewModelProviders
2020
import android.os.Bundle
21+
import android.support.v7.app.AppCompatActivity
2122
import android.support.v7.widget.GridLayoutManager
2223
import android.support.v7.widget.RecyclerView
2324
import com.hossainkhan.android.demo.R
24-
import com.hossainkhan.android.demo.data.AppDataStore
2525
import com.hossainkhan.android.demo.layoutpreview.LayoutPreviewBaseActivity
2626
import com.hossainkhan.android.demo.layoutpreview.LayoutVisibilityGoneActivity
27+
import com.hossainkhan.android.demo.viewmodel.LayoutPreviewViewModelFactory
2728
import dagger.android.AndroidInjection
2829
import timber.log.Timber
2930
import javax.inject.Inject
3031

31-
class MainActivity : AppCompatActivity() {
32+
/**
33+
* A list activity that shows all the available example demo layouts.
34+
*/
35+
class LayoutBrowseActivity : AppCompatActivity() {
3236
@Inject
33-
lateinit var appDataStore: AppDataStore
37+
internal lateinit var viewModelFactory: LayoutPreviewViewModelFactory
3438

3539
private lateinit var recyclerView: RecyclerView
3640
private lateinit var viewAdapter: RecyclerView.Adapter<*>
3741
private lateinit var viewManager: RecyclerView.LayoutManager
3842

43+
private lateinit var viewModel: LayoutBrowseViewModel
44+
3945
override fun onCreate(savedInstanceState: Bundle?) {
4046
AndroidInjection.inject(this)
4147
super.onCreate(savedInstanceState)
4248
setContentView(R.layout.activity_main)
4349

44-
Timber.d("Got data: ${appDataStore.isFirstTime()}")
45-
appDataStore.updateFirstTimeUser(false)
50+
viewModel = ViewModelProviders.of(this, viewModelFactory)
51+
.get(LayoutBrowseViewModel::class.java)
52+
53+
setupLayoutInfoAdapter(viewModel)
54+
}
4655

56+
private fun setupLayoutInfoAdapter(viewModel: LayoutBrowseViewModel) {
4757
viewManager = GridLayoutManager(this, resources.getInteger(R.integer.grid_column_count))
4858
viewAdapter = LayoutBrowseAdapter(
49-
data = appDataStore.layoutStore.supportedLayoutInfos,
59+
viewModel = viewModel,
60+
lifecycleOwner = this,
5061
itemSelectedListener = this::onLayoutItemSelected)
5162

5263
recyclerView = findViewById<RecyclerView>(R.id.recycler_view).apply {

app/src/main/java/com/hossainkhan/android/demo/browse/LayoutBrowseAdapter.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

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

19+
import android.arch.lifecycle.LifecycleOwner
20+
import android.arch.lifecycle.Observer
1921
import android.support.v7.widget.RecyclerView
2022
import android.view.LayoutInflater
2123
import android.view.View
@@ -26,9 +28,20 @@ import com.hossainkhan.android.demo.R
2628
import com.hossainkhan.android.demo.data.LayoutInformation
2729

2830
class LayoutBrowseAdapter(
29-
private val data: List<LayoutInformation>,
31+
viewModel: LayoutBrowseViewModel,
32+
lifecycleOwner: LifecycleOwner,
3033
private val itemSelectedListener: (Int) -> Unit) : RecyclerView.Adapter<LayoutBrowseAdapter.ViewHolder>() {
3134

35+
private var data: List<LayoutInformation> = emptyList()
36+
37+
init {
38+
viewModel.layoutInfos.observe(lifecycleOwner, Observer {
39+
data = it!!
40+
notifyDataSetChanged()
41+
})
42+
}
43+
44+
3245
// Provide a reference to the views for each data item
3346
// Complex data items may need more than one view per item, and
3447
// you provide access to all the views for a data item in a view holder.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.browse
18+
19+
import android.arch.lifecycle.LiveData
20+
import android.arch.lifecycle.MutableLiveData
21+
import android.arch.lifecycle.ViewModel
22+
import com.hossainkhan.android.demo.data.AppDataStore
23+
import com.hossainkhan.android.demo.data.LayoutInformation
24+
import timber.log.Timber
25+
26+
class LayoutBrowseViewModel(
27+
appDataStore: AppDataStore) : ViewModel() {
28+
29+
private val layoutInfoListLiveData = MutableLiveData<List<LayoutInformation>>()
30+
31+
val layoutInfos: LiveData<List<LayoutInformation>>
32+
get() = layoutInfoListLiveData
33+
34+
init {
35+
Timber.d("Got data: ${appDataStore.isFirstTime()}")
36+
appDataStore.updateFirstTimeUser(false)
37+
38+
39+
layoutInfoListLiveData.value = appDataStore.layoutStore.supportedLayoutInfos
40+
}
41+
}

app/src/main/java/com/hossainkhan/android/demo/dagger/MainActivityModule.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package com.hossainkhan.android.demo.dagger
1818

1919
import android.app.Activity
20-
import com.hossainkhan.android.demo.browse.MainActivity
20+
import com.hossainkhan.android.demo.browse.LayoutBrowseActivity
2121
import dagger.Binds
2222
import dagger.Module
2323
import dagger.android.ActivityKey
@@ -29,7 +29,7 @@ abstract class MainActivityModule {
2929

3030
@Binds
3131
@IntoMap
32-
@ActivityKey(MainActivity::class)
32+
@ActivityKey(LayoutBrowseActivity::class)
3333
abstract fun bindMainActivityInjectorFactory(
3434
builder: MainActivitySubcomponent.Builder): AndroidInjector.Factory<out Activity>
3535

app/src/main/java/com/hossainkhan/android/demo/dagger/MainActivitySubcomponent.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616

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

19-
import com.hossainkhan.android.demo.browse.MainActivity
19+
import com.hossainkhan.android.demo.browse.LayoutBrowseActivity
2020
import dagger.Subcomponent
2121
import dagger.android.AndroidInjector
2222

2323
@Subcomponent
24-
interface MainActivitySubcomponent : AndroidInjector<MainActivity> {
24+
interface MainActivitySubcomponent : AndroidInjector<LayoutBrowseActivity> {
2525
@Subcomponent.Builder
26-
abstract class Builder : AndroidInjector.Builder<MainActivity>()
26+
abstract class Builder : AndroidInjector.Builder<LayoutBrowseActivity>()
2727
}
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: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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.browse.LayoutBrowseViewModel
22+
import com.hossainkhan.android.demo.data.AppDataStore
23+
import com.hossainkhan.android.demo.layoutpreview.LayoutInfoViewModel
24+
import javax.inject.Inject
25+
26+
/**
27+
* The [ViewModelProvider.Factory] that provides all the ViewModels for the activities and fragments.
28+
*/
29+
class LayoutPreviewViewModelFactory @Inject constructor(
30+
private val dataStore: AppDataStore
31+
32+
) : ViewModelProvider.Factory {
33+
@Suppress("UNCHECKED_CAST")
34+
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
35+
return when {
36+
modelClass.isAssignableFrom(LayoutInfoViewModel::class.java) -> {
37+
LayoutInfoViewModel(dataStore) as T
38+
}
39+
modelClass.isAssignableFrom(LayoutBrowseViewModel::class.java) -> {
40+
LayoutBrowseViewModel(dataStore) as T
41+
}
42+
else -> throw IllegalArgumentException("Unknown ViewModel class")
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)