0

I have an app where I'm using SharedPreferences. When I try to get the saved name this error occurs: java.lang.RuntimeException: Cannot create an instance of class com.meetme.viewmodel.SharedPrefViewModel

What could be wrong in code below? Any ideas?

SharedPrefViewModel class:

class SharedPrefViewModel(context: Context): ViewModel() {
    private var sharedPrefRepository: SharedPrefRepository = SharedPrefRepository(context.applicationContext)
 
    fun putString(name: String) {
        sharedPrefRepository.putString(name)
    }
    fun getString() : String? {
        return sharedPrefRepository.getString() <-- here is the error
    }
}

SharedPrefRepository class:

class SharedPrefRepository(context: Context) {
    private val pref: SharedPreferences =
        context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)

    var name: String?
    get() = pref.getString(PREF_LOGGED_IN, "")
    set(value) = pref.edit().putString(PREF_LOGGED_IN, value).apply()
    
    fun putString(name: String) {
        this.name = name
    }

    fun getString() : String? {
        return this.name
    }
}

Composable, where I'm using prefs to save a name:

@Composable
fun BasicInfoScreen(navController: NavHostController) {
    var context = LocalContext.current.applicationContext
    val sharedPrefViewModel = SharedPrefViewModel(context)

    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.White)
            .padding(16.dp)
    ) {
        ...
        Button(
            onClick = {
                sharedPrefViewModel.putString("default") <---- here
                navController.navigate(RootGraphItem.Location.route)
            },
            modifier = Modifier
                .fillMaxWidth()
                .height(50.dp)
                .background(Color(0xFF0090FF)),
            colors = ButtonDefaults.buttonColors(backgroundColor = Color(0xFF0090FF))
        ) {
            Text(text = "Next", style = TextStyle(color = Color.White))
        }
    }
}

Composable that gets a value from prefs:

@Composable
fun LocationScreen(navController: NavHostController) {
    val sharedPrefViewModel: SharedPrefViewModel = viewModel()

    println(sharedPrefViewModel.getString()) <-- get name

    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.White)
            .padding(16.dp)
    ) {
       ...
    }
}
Yunnane
  • 85
  • 9

1 Answers1

1

All ViewModels are created using a Factory. If your ViewModel constructor doesn't fit something that the default factory can handle, then you have to create your own ViewModelProvider.Factory implementation that can create your ViewModel, and you must pass it to the viewModel() function in your composable.

You must never directly call your ViewModel constructor except inside its factory! This: val sharedPrefViewModel = SharedPrefViewModel(context) will cause problems because your ViewModel is not created through the ViewModelProvider, so it is not registered and so it will not have a proper lifecycle and it will not use a single shared instance by different screens/composables that use it.

The default factory is only capable of creating instances of your ViewModel if your ViewModel constructor's arguments are one of the following:

  • constructor() Empty constructor (no arguments)
  • constructor(savedStateHandle: SavedStateHandle)
  • constructor(application: Application)
    • Also requires that you subclass AndroidViewModel, not just ViewModel.
  • constructor(application: Application, savedStateHandle: SavedStateHandle)
    • Also requires that you subclass AndroidViewModel, not just ViewModel.

Notice, your constructor takes a single Context argument, so it doesn't fit any of the above. If you want to avoid having to create a factory, which is a lot of boilerplate, I would change your class as follows so it fits the third one above:

class SharedPrefViewModel(application: Application): AndroidViewModel(application) {
    private val context: Context = application.applicationContext

    //...
}

And don't forget to fix the line where you called your constructor instead of calling viewModel()!

Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • How can I get the application parameter from the composable functions? – Yunnane Mar 23 '23 at 14:06
  • You don't need it! Just create your view model using `viewModel()` since you must never call your ViewModel constructor, like I said. The default factory will take care of passing it the application instance. – Tenfour04 Mar 23 '23 at 14:07
  • Omg! Thank you so much. Works now, I missed the last line in your answer. You're the best! – Yunnane Mar 23 '23 at 14:24