I'm new in kotlin and ı 'm trying to apply mvvm design pattern in my simple project to understand. I have a model,view,viewmodel. view package has mainactivity and 2 fragments. There are tablayout and viewpager views in mainactivity, I am trying to access them from stopwatchviewmodel in viewmodel, but I cannot access them. stopwatchviewmodel is a class but I can access from fragments and mainactivity. How can ı solve this issue?
MainActivity.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.MainActivity">
<com.google.android.material.tabs.TabLayout
android:id="@ id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="kronometre" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="zamanlayıcı" />
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager.widget.ViewPager
android:id="@ id/viewPager" <!-- the data ı want to reach-->
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/tabLayout">
</androidx.viewpager.widget.ViewPager>
StopWatchViewModel.kt
class StopWatchViewModel(val context: Context):ViewModel() {
private fun setUpTabs()
{
val fragmentManager = (context as FragmentActivity).supportFragmentManager
val adapter = ViewPageModelAdapter(fragmentManager)
adapter.addFragment(StopWatchFragment(),"stopWatchFragment")
adapter.addFragment(TimerFragment(),"timerFragment")
viewpager.adapter = adapter // <--- ı cannot access viewpager
}
}
build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-android-extensions' // <--- I also added kotlin extensions.
}
android {
compileSdk 31
defaultConfig {
applicationId "com.enestigli.kronometre"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures{
dataBinding = true
viewBinding = true
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
}
CodePudding user response:
Setting up Views and Fragments is part of the 'View' layer so it shouldn't really be in de ViewModel in the first place. Let this code live in the Fragment or Activity classes that actually contain these views.
That being said, generally speaking if your ViewModel needs anything you can pass it those dependencies either in the constructor, in the method that uses them or through some dependency injection framework.
In this case, you could pass the Context and ViewPager objects to the setUpTabs function:
fun setUpTabs(context: Context, viewPager: ViewPager) {
...
}
And then call it from the fragment with the correct values.
Now, since setting up Views is not part of the ViewModels responsibility, you could refactor it to some setup where the ViewModel decides which fragments should be added, but the view actually handles the implementation.
The ViewModel decides what to show, but doesn't require any of the Views dependencies:
class SomeViewModel() : ViewModel() {
fun getTabsToShow(): Map<String, Fragment> {
return mapOf(
"stopWatchFragment" to StopWatchFragment(),
"timerFragment" to TimerFragment())
}
}
The Fragment, for example it could also be an Activity, then uses this information to build up the correct views:
class SomeFragment : Fragment() {
override fun onViewCreated(view: View) {
val tabs = viewModel.getTabsToShow()
val fragmentManager = requireActivity().supportFragmentManager
val adapter = ViewPageModelAdapter(fragmentManager)
for ((tag, fragment) in tabs) {
adapter.addFragment(fragment, tag)
}
viewpager.adapter = adapter
}
}