I am programing an app in kotlin using MVVM structur. i tried to instantiate my viewmodel through my factory. Though im getting an error when i start the app. It says this `
java.lang.RuntimeException: Cannot create an instance of class com.example.paperseller.ui.menu.ViewModels.AuthViewModel
I have no clue at all why im getting this trouble. My code looks like this
I have already tried to skip the factory implementation.
I would not use this way of implementing mvvm in kotlin nowadays. I would take a look at renewed tutorials 2021.
LoginRegister_fragment:
package com.example.paperseller.ui.menu
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.paperseller.Data.User
import com.example.paperseller.R
import com.example.paperseller.databinding.LoginRegisterFragmentBinding
import com.example.paperseller.ui.menu.ViewModels.AuthViewModel
class LoginRegister_fragment : Fragment(R.layout.login_register_fragment) {
private var _binding : LoginRegisterFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
{
_binding = LoginRegisterFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initializeUI()
}
private fun initializeUI()
{
val factory = InjectorUtils.providePaperSellerViewModelFactory()
val viewModel : AuthViewModel by viewmodels()
viewModel.message.observe(this)
{
message ->
Toast.makeText(requireActivity(), message, Toast.LENGTH_LONG).show()
}
binding.registerButton.setOnClickListener()
{
val user = User(binding.emailText.text.toString(), binding.passwordText.text.toString());
viewModel.register(user)
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
ViewModelFactory:
package com.example.paperseller.ui.menu.ViewModelFactory
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.paperseller.Data.PaperSellerRepository
import com.example.paperseller.ui.menu.ViewModels.AuthViewModel
class PaperSellerViewModelFactory(private val paperSellerRepository: PaperSellerRepository) : ViewModelProvider.NewInstanceFactory()
{
#Suppress("UNCHECKED_CAST")
override fun <T:ViewModel?> create(modelClass: Class<T>):T{
return AuthViewModel(paperSellerRepository) as T
}
}
I tried to follow this tutorial:
https://resocoder.com/2018/09/07/mvvm-on-android-crash-course-kotlin-
This tutorial is 4 years old. Tip to people seeing this question later. Check tutorials on youtube that is up to date this helped me alot.
Your ViewModel takes in a repository and since then You have to use the ViewModelFactory. You can either use ViewModelProvider:
val factory = InjectorUtils.providePaperSellerViewModelFactory()
val viewModel = ViewModelProvider(
this,
factory)
.get(AuthViewModel::class.java)
or, as #IR42 has pointed out, You can use viewModels delegate:
val viewModel: AuthViewModel by viewModels { factory }
Related
Android studio shows errors in the lines
REPOSITORY.insert(note){ onSuccess() }
in the "AddNewNoteFragmentViewModel" and in the lines
viewModel.insert(AppNote(name = name, text = text)){ view?.findNavController()?.navigate(R.id.action_addNewNoteFragment_to_mainFragment)}
in the file "AddNewNoteFragment"
I realized that the error is related to streams, but I do not know how to solve it
If anyone knows, please help, I could not find anything worthwhile on the Internet
AddNewNoteFragment
package com.example.notes.fragments.add_new_note
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import com.example.notes.R
import com.example.notes.databinding.FragmentAddNewNoteBinding
import com.example.notes.model.AppNote
import com.example.notes.utilits.showToast
class AddNewNoteFragment : Fragment() {
private var _binding: FragmentAddNewNoteBinding? = null
val binding get() = _binding!!
private lateinit var viewModel: AddNewNoteFragmentViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentAddNewNoteBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onStart() {
super.onStart()
initialization()
}
private fun initialization() {
viewModel = ViewModelProvider(this).get(AddNewNoteFragmentViewModel::class.java)
binding.buttonAddNote.setOnClickListener {
val name = binding.inputNameNote.text.toString()
val text = binding.inputTextNote.text.toString()
if (name.isEmpty()){
showToast("Введите имя заметки")
} else{
viewModel.insert(AppNote(name = name, text = text)){
view?.findNavController()?.navigate(R.id.action_addNewNoteFragment_to_mainFragment)
}
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
AddNewNoteFragmentViewModel
package com.example.notes.fragments.add_new_note
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.example.notes.model.AppNote
import com.example.notes.utilits.REPOSITORY
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class AddNewNoteFragmentViewModel(application: Application): AndroidViewModel(application) {
fun insert(note: AppNote,onSuccess:()-> Unit) =
viewModelScope.launch (Dispatchers.IO){
REPOSITORY.insert(note){
onSuccess()
}
}
}
Error screen
enter image description here
Problem solved
In AddNewNoteFragmentViewModel requires Dispatchers.IO to be changed to Dispatchers.Main
I'm trying change some text after load a fragment. It will be location tracker in future. So I'm testing change of the text.
build.gradle module:
android {
buildFeatures{
dataBinding = true
viewBinding = true
}
}
start.xml:
<TextView
android:id="#+id/location"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:text="Current GPS Location"
android:textColor="#FF0000"
android:textSize="30sp"
android:textStyle="bold" />
Start.kt:
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.databinding.DataBindingUtil.setContentView
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.example.basic.databinding.StartBinding
import android.util.Log
class Start : Fragment() {
private lateinit var binding: StartBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = StartBinding.inflate(layoutInflater)
binding.location.setText("Hello World")
}
}
Application runs without problem but setText Not works. Do you know why?
You are initializing your binding but not adding it to the view
onCreateView(inflater... container...) {
binding = StartBinding.inflate(layoutInflater, container, false)
return binding.root
}
then following the life cycle usage previous to binding, you have to make the change after the view is created
onViewCreated(...) {
binding.location.setText("Hello World")
}
A small comment, your naming doesn't follow conventions:
StartFragment
fragment_start
You should do the binding in onCreateView instead of onCreate as in onCreate() the fragment view is not created yet.
So transfer below in onCreateView
binding = StartBinding.inflate(layoutInflater)
binding.location.setText("Hello World")
For more info check fragment lifecycle
Thanks for answers!
Solution is very simple.
Here is solution for people who will find this question:
class Start : Fragment() {
lateinit var binding : StartBinding //this must be at the begin of your class
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = StartBinding.inflate(layoutInflater,container,false) //this must be here in onCreateView
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.textView.setText("Hello World") // and now setText works in onViewCreated
}
}
I'm a relative beginner to android programming and I want to have it so my fragments display the layout files which are assigned to them through a view model. I'll be providing only one fragment and view model pair as the 3 others which I have are practically the same except some names and text is swapped around.
Here's my TimetableFragment.kt
package com.example.intranetapp
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
class TimetableFragment : Fragment() {
private lateinit var timetableViewModel: TimetableViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
timetableViewModel =
ViewModelProvider(this).get(TimetableViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_news, container, false)
val textView: TextView = root.findViewById(R.id.text_news)
timetableViewModel.text.observe(viewLifecycleOwner, Observer {
textView.text = it
})
return root
}
}
Here's my TimetableViewModel.kt
package com.example.intranetapp
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class TimetableViewModel : ViewModel() {
private val _text = MutableLiveData<String>().apply {
value = "This is timetable Fragment"
}
val text: LiveData<String> = _text
}
And here's my MainActivity.kt
package com.example.intranetapp
import android.os.Bundle
import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(setOf(
R.id.timetableFragment, R.id.dueworkFragment, R.id.newsFragment, R.id.gradesFragment))
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
}
I'm not getting any errors when running the application, i'm more just wondering how i can make it so my XML file Timetable.XML is shown when creating the viewmodel rather than text in the middle of the screen which says "This is Timetable fragment"
I'm sorry if i come across as a dumb coder, i'm just struggling to fix this issue and this was the first place to come to mind as google searches aren't helping
The problem is here:
private val _text = MutableLiveData<String>().apply {
value = "This is timetable Fragment"
}
Setting the value happens when ViewModel is initializing. But you start observing this LiveData after initializing, so you loss this value.
You should add method in your viewModel:
fun setTextValue() {
_text.value = "This is timetable Fragment"
}
and call it after timetableViewModel.text.observe()
So I'm trying to create a tabbed app in Kotlin and I have chosen the default one they made for you to practice but I can't figure out how to get the buttons working `
package com.example.android_app.ui.home
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.example.android_app.R
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_home.*
class HomeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Program Buttons
logout.setOnClickListener{
val intent = Intent(this, sign_in::class.java)
startActivity(intent)
}
}
private lateinit var homeViewModel: HomeViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
homeViewModel =
ViewModelProviders.of(this).get(HomeViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_home, container, false)
homeViewModel.text.observe(viewLifecycleOwner, Observer {
})
return root
}
}
`
As far as I know this should work. The problem is not in the button but in Intent. My button is already defined and has no errors but there is a red line under Intent even though it's imported. The error message is below.
public constructor Intent(p0: Context!, p1: Class<*>!) defined in android.content.Intent
public constructor Intent(p0: String!, p1: Uri!) defined in android.content.Intent
You need to pass context into the constructor of Intent instead of this.
val intent = Intent(context, sign_in::class.java)
I am using libGDX inside android project as fragment all with kotlin and its working fine.
What i am trying to do is call a method of libgdx part of project from android part(MainActivity) of the project.
So for example if user presses on button which is made by android part,object in game will move.
First of all this is the project structure:
MainActivity:
package com.krytasoft.androidwithlibgdx
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
//import android.support.v4.app.Fragment throws unresolved error.without this compiles fine and works but shows type mismatch error.
import com.badlogic.gdx.backends.android.AndroidFragmentApplication
import com.krytasoft.gdxandroid.AndroidGameFragment
class MainActivity : AppCompatActivity(), AndroidFragmentApplication.Callbacks {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val libgdxGameFragment:AndroidGameFragment = AndroidGameFragment()
val button = findViewById<Button>(R.id.openFlexBoxTestButton)
val moveRightButton = findViewById<Button>(R.id.moveRightButton)
//never mind if this supportFragmentManager... shows type mismatch error.Its working. this line puts libgdx into fragment.fragment is similar to component in react.
supportFragmentManager.beginTransaction().replace(R.id.fragment_container, libgdxGameFragment, AndroidGameFragment::class.java.simpleName).commit()
button.setOnClickListener{
val intent = Intent(this, FlexBoxTestActivity::class.java)
startActivity(intent)
}
moveRightButton.setOnClickListener {
libgdxGameFragment.moveRight()
}
}
override fun exit() {
}
}
Here in MainActivity, notice moveRightButton.Its calling moveRight() function from fragment.
AndroidFragment class:
package com.krytasoft.gdxandroid
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration
import com.badlogic.gdx.backends.android.AndroidFragmentApplication
import com.krytasoft.mygdxgame.core.MyGdxGame
class AndroidGameFragment : AndroidFragmentApplication() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
val config = AndroidApplicationConfiguration()
return initializeForView(MyGdxGame(), config)
}
override fun startActivity(intent: Intent?) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
fun moveRight(){
MyGdxGame().moveRight()
}
}
MyGDX Game:
package com.krytasoft.mygdxgame.core
import com.badlogic.gdx.ApplicationAdapter
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.GL20
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.Sprite
import com.badlogic.gdx.graphics.g2d.SpriteBatch
class MyGdxGame : ApplicationAdapter() {
lateinit var batch: SpriteBatch
lateinit var img: Texture
lateinit var sprite:Sprite
override fun create() {
batch = SpriteBatch()
img = Texture("badlogic.jpg")
sprite = Sprite(img)
println("create done from libgdx")
}
override fun render() {
Gdx.gl.glClearColor(1f, 0f, 0f, 1f)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
batch.begin()
batch.draw(sprite, 50f, 50f)
batch.end()
}
override fun dispose() {
batch.dispose()
img.dispose()
}
fun moveRight(){
sprite.x +=50f; // throws lateinit var sprite is uninitialzied error.
}
}
So what i expect is after pressing move right button,eventually call fun moveRight in mylibgdxgame and change position of sprite.
kotlin.UninitializedPropertyAccessException: lateinit property sprite has not been initialized
eventhough its initialized.But for some reason its not seen.
I uploaded my project to github:
https://github.com/lastpeony/libgdx-in-android-kotlin
Call create() method before using MyGdxGame instance. Also use a single instance of MyGdxGame across fragment.
class AndroidGameFragment : AndroidFragmentApplication() {
val myGdxGame = MyGdxGame()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
val config = AndroidApplicationConfiguration()
myGdxGame.create()
return initializeForView(myGdxGame, config)
}
fun moveRight(){
myGdxGame .moveRight()
}
}