I’m following Android Developers course in Android Basics with Compose. I’m on Unit 6.2 Use Room for data persistence (the level of my current knowledge in Android App Development thus far, as I’m starting out).
I’m working on a notes app called Outlook Planner
A simple notes app + simple weather forecaster. And I’m basing my app structure to the Inventory App found in the current level I’m in the course.
My view models HomeViewModel and MakePlanViewModel both pull from the AppViewModelProvider to gather info form the SQLite + Room database for displaying list of notes and insert/update operations respectively. And they will be used by pages “Home” and “MakePlan” respectively.
However, at runtime, the app crashes and this error that I can’t solve is printed in this photo
Or
java.lang.ClassCastException: android.app.Application cannot be cast to com.example.outlook.planner.OutlookPlannerApplication
at com.example.outlook.planner.ui.AppViewModelProviderKt.outlookPlannerApplication(AppViewModelProvider.kt:42)
at com.example.outlook.planner.ui.AppViewModelProvider$Factory$1$2.invoke(AppViewModelProvider.kt:32)
at com.example.outlook.planner.ui.AppViewModelProvider$Factory$1$2.invoke(AppViewModelProvider.kt:30)
I was expecting that the app would work normally; I would access my database to display notes in the Home page, and insert/edit notes from the MakePlan page.
Unfortunately, with my current, limited knowledge in Android App Development, I don’t know where to start diagnosing the issue and fixing it.
I’m doing this app for a school project, and I really don’t wanna build from the ground-up as I’ve already put a lot of work on this (and doesn’t help that tutorials online on building a notes app in Kotlin + Jetpack Compose are a few years old and don’t have a singular, basic method to follow that resembles anything Google puts out in their examples).
Any help will be most appreciated. Thanks.
EDIT Here's code for you to review:
class OutlookPlannerApplication : Application() {
/**
* AppContainer instance used by the rest of classes to obtain dependencies
*/
lateinit var container: AppContainer
override fun onCreate() {
super.onCreate()
container = AppDataContainer(this)
}
package com.example.outlook.planner.ui
import android.app.Application
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.example.outlook.planner.OutlookPlannerApplication
import com.example.outlook.planner.ui.pages.home.HomeViewModel
import com.example.outlook.planner.ui.pages.makeplan.MakePlanViewModel
/**
* Provides Factory to create instance of ViewModel for the entire Outlook Planner app
*/
object AppViewModelProvider {
val Factory = viewModelFactory {
/**
* Initializer for [MakePlanViewModel]
*/
initializer {
MakePlanViewModel(
planRepository = outlookPlannerApplication().container.planRepository
)
// MakePlanViewModel()
}
/**
* Initializer for [HomeViewModel]
*/
initializer {
HomeViewModel(
planRepository = outlookPlannerApplication().container.planRepository
)
}
}
}
/**
* Extension function to queries for [Application] object and returns an instance of
* [OutlookPlannerApplication].
*/
fun CreationExtras.outlookPlannerApplication(): OutlookPlannerApplication =
(this[AndroidViewModelFactory.APPLICATION_KEY] as OutlookPlannerApplication)
/**
* ViewModel to retrieve all items in the Room database.
*/
class HomeViewModel(planRepository: PlanRepository): ViewModel() {
val homeUiState: StateFlow<HomeUiState> =
planRepository.getPlanAll().map { HomeUiState(it) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS),
initialValue = HomeUiState()
)
companion object {
private const val TIMEOUT_MILLIS = 5_000L
}
}
class MakePlanViewModel(
savedStateHandle: SavedStateHandle? = null,
private val planRepository: PlanRepository
): ViewModel() {
/**
* Make Plan UI state
*/
var makePlanUiState by mutableStateOf(MakePlanUiState())
private set
/**
* Initialize private values in presence of an existing Plan object
*/
fun initialize(planEntity: PlanEntity) {
/** DO SOMETHING */
}
// init {
// if (savedStateHandle != null) {
// viewModelScope.launch {
// makePlanUiState = planRepository.getPlanOne(planId)
// .filterNotNull()
// .first()
// .toItemUiState(true)
// }
// }
// }
/**
* Check that no fields are empty
*/
private fun validateInput(planCheck: Plan = makePlanUiState.plan): Boolean {
return with(planCheck) {
note.isNotBlank()
}
}
/**
* Updates the [makePlanUiState] with the value provided in the argument. This method also triggers
* a validation for input values.
*/
fun updateUiState(planUpdated: Plan) {
makePlanUiState = MakePlanUiState(
plan = planUpdated,
fieldNotEmptyAll = validateInput(planUpdated)
)
}
/**
* Insert + Update current plan
*/
suspend fun planUpsert() {
if (validateInput()) {
planRepository.planUpsert(makePlanUiState.plan.toEntity())
}
}
}
Any more, please let me know, or git clone my projct here and open in Android Studio
You probably didn't register your custom Application in your manifest.
Per the documentation (emphasis mine):
You can provide your own implementation by creating a subclass and specifying the fully-qualified name of this subclass as the "android:name" attribute in your AndroidManifest.xml's tag.
So:
In AndroidManifest.xml
should be something like:
<application
android:name="com.example.outlook.planner.OutlookPlannerApplication"
....
</application>
Without that the system doesn't know about your custom application and initializes a default Application
class instance which leads to your error when trying to cast it to your expected type.