Home > Enterprise >  Cannot create an instance of ViewModel
Cannot create an instance of ViewModel

Time:11-02

I'm building a project using ViewModel, Compose and Hilt. I can't switch to BookListScreen because I am getting the error in the title. Points that I think are important:

  • onItemClick = { navController.navigate(Screen.BookListScreen.route) } in MainScreen.
  • composable(route = Screen.BookListScreen.route) { BookListScreen(navController) }

in MainActivity.

  • And @AndroidEntryPoint annotation. I've added this to MainActivity but do I need to add it elsewhere?

Main Activity:

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LOTRTheme {
                // A surface container using the 'background' color from the theme
                Surface(color = MaterialTheme.colors.background) {
                    val navController = rememberNavController()
                    NavHost(
                        navController = navController,
                        startDestination = Screen.MainScreen.route
                    ) {
                        composable(
                            route = Screen.MainScreen.route
                        ) {
                            MainScreen(navController)
                        }
                        composable(
                            route = Screen.BookListScreen.route
                        ) {
                            BookListScreen(navController)
                        }
                    }
                }
            }
        }
    }
}

Main Screen:

@Composable
fun MainScreen(navController: NavController) {
    val configuration = LocalConfiguration.current
    when (configuration.orientation) {
        Configuration.ORIENTATION_LANDSCAPE -> {
            SectionsScreenRotate(navController)
        }
        else -> {
            SectionsScreen(navController)
        }
    }
}

@Composable
fun SectionsScreen(navController: NavController) {
    Column(
        modifier = Modifier.padding(10.dp)
    ) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f)
                .padding(10.dp)
        ) {
            SectionItem(
                section = Section("Characters", R.drawable.charachtersdraw),
                onItemClick = {}
            )
        }

        Column(
            modifier = Modifier
                .fillMaxHeight()
                .weight(1f)
                .padding(10.dp)
        ) {
            SectionItem(
                section = Section("Movies", R.drawable.moviesdraw),
                onItemClick = {}
            )
        }
        Column(
            modifier = Modifier
                .fillMaxHeight()
                .weight(1f)
                .padding(10.dp)
        ) {
            SectionItem(
                section = Section("Books", R.drawable.booksdraw),
                onItemClick = { navController.navigate(Screen.BookListScreen.route) }
            )
        }
    }
}

@Composable
fun SectionsScreenRotate(navController: NavController) {
    Row(
        modifier = Modifier.padding(10.dp)
    ) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f)
                .padding(10.dp)
        ) {
            SectionItem(
                section = Section("Characters", R.drawable.charachtersdraw),
                onItemClick = {}
            )
        }

        Column(
            modifier = Modifier
                .fillMaxHeight()
                .weight(1f)
                .padding(10.dp)
        ) {
            SectionItem(
                section = Section("Movies", R.drawable.moviesdraw),
                onItemClick = { navController.navigate(Screen.BookListScreen.route)}
            )
        }
        Column(
            modifier = Modifier
                .fillMaxHeight()
                .weight(1f)
                .padding(10.dp)
        ) {
            SectionItem(
                section = Section("Books", R.drawable.booksdraw),
                onItemClick = {}
            )
        }
    }

}

@Composable
fun SectionItem(section: Section, onItemClick: () -> Unit) {
    Card(
        modifier = Modifier
            .fillMaxSize()
            .clickable { onItemClick() },
        shape = RoundedCornerShape(15.dp),
        elevation = 5.dp,
    ) {
        Box(modifier = Modifier.fillMaxSize()) {
            Image(
                painter = painterResource(id = section.imageId),
                contentDescription = section.title,
                contentScale = ContentScale.Crop,
                modifier = Modifier.fillMaxSize()
            )
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(12.dp),
                contentAlignment = Alignment.BottomCenter
            ) {
                Text(
                    section.title, style = TextStyle(
                        color = Color.White,
                        fontSize = 16.sp,
                        fontWeight = FontWeight.Bold,
                    )

                )
            }

        }

    }
}

BookListScreen:

@Composable
fun BookListScreen(
    navController: NavController,
    bookListViewModel: BookListViewModel = viewModel()
) {
    val state = bookListViewModel.state.value

    Box(modifier = Modifier.fillMaxSize()) {
        LazyColumn(modifier = Modifier.fillMaxSize()) {
            items(state.books) { book ->
                BookListItem(
                    book = book
                )
            }
        }
        if (state.error.isNotBlank()) {
            Text(
                text = state.error,
                color = MaterialTheme.colors.error,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 20.dp)
                    .align(Alignment.Center)
            )
        }
        if (state.isLoading) {
            CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
        }
    }
}

ViewModel:

@HiltViewModel
class BookListViewModel @Inject constructor(
    private val bookRepository: BookRepository
) : ViewModel(){

    val state = mutableStateOf(BookListState())

    init {
        getBooks()
    }

    private fun getBooks() {
        bookRepository.getBooks().onEach { result ->
            when(result) {
                is Resource.Success -> {
                    state.value = BookListState(books = result.data ?: emptyList())
                }
                is Resource.Error -> {
                    state.value = BookListState(error = result.message ?: "Beklenmedik bir hata oluştu")
                }
                is Resource.Loading -> {
                    state.value = BookListState(isLoading = true)
                }
            }
        }.launchIn(viewModelScope)
    }

}

AppModule:

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun getLotrApi() : LotrApi {
        return Retrofit.Builder()
            .baseUrl(Constants.BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(LotrApi::class.java)
    }

    @Provides
    @Singleton
    fun getBookRepository(api: LotrApi) : BookRepository {
        return BookRepository(api)
    }
}

BookRepository:

    class BookRepository @Inject constructor(
    private val api : LotrApi
){

    fun getBooks() : Flow<Resource<List<Book>>> = flow {
        try {
            emit(Resource.Loading<List<Book>>())
            val books = api.getBookList().BookListDtoToBookList()
            emit(Resource.Success<List<Book>>(books))
        }
        catch (e: HttpException) {
            emit(Resource.Error<List<Book>>(e.localizedMessage ?: "Beklenmedik hata"))
        }
        catch(e: IOException) {
            emit(Resource.Error<List<Book>>(e.localizedMessage ?: "Lütfen internet bağlantınızı kontrol edin"))
        }

    }
}

Screen:

sealed class Screen(val route : String){
    object BookListScreen : Screen("book_list")
    object BookChaptersScreen : Screen("book_chapters")
    object MainScreen : Screen("main_screen")
}

CodePudding user response:

While using Hilt along with Navigation Library, you need to use hiltViewModel() instead of viewModel() to inject ViewModels. Check out documentation for more info.

  • Related