when i try to initialize data class it gives me error, kotlin.UninitializedPropertyAccessException: lateinit property article_ has not been initialized. How do i resolve this error and whats way to initialize data class.
model class
data class Article(
val id: Int,
val author: String,
val content: String,
val description: String?)
MainActivity
class TestActivity : AppCompatActivity() {
lateinit var binding: ActivityTestBinding
lateinit var article_: List<Article>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this,R.layout.activity_test)
val mainAdapter = MainAdapter(article_)
binding.recView.apply {
this.layoutManager = LinearLayoutManager(this#TestActivity)
this.adapter = mainAdapter
}
}
}
Change code like this
binding = DataBindingUtil.setContentView(this,R.layout.activity_test) article_ = ArrayList()
ANSWER:
In onCreate() before this line val mainAdapter = MainAdapter(article_) just initialize the article_ by writing article_ = ArrayList() and the error will be gone.
Cause of Error
You are encountering this error because You need to initialize the lateinit variable article_ before using it and you are not doing it.
If you want to check that either the variable is initialized or not then use ::article_.isInitialized.
Feel free to ask if something is unclear.
lateinit should only be used when you have a specific reason not to initialize the variable on construction. Here I can see no reason not to make it
var article_: List<Article> = listOf()
or perhaps
val article_: MutableList<Article> = mutableListOf()
and guarantee such error can't happen in the first place.
Related
imageSorry, I'm new to Java/Kotlin mobile apps...
Below code snippet from RegisterFragment.kt which is the main class:-
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val username = binding.text
PackageSdk.getInstance().hasDuplicateUserKey(**username**, object : PackageResponseCallback<ResultResponse> {
override fun onSuccess(result: ResultResponse) {
Log.i(TAG, "Result code : " + result.rtCode)
}
override fun onFailed(errorResult: ErrorResult) {
Log.e(TAG, "Error code : " + errorResult.errorCode)
}
})
whereas below is the data class named RegisteredUserView.kt
data class RegisteredUserView(
val username: String
//... other data fields that may be accessible to the UI
)
I usually used toString() to the param value of "username" but I will get this bug
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' on a null object reference.
but if I just leave only the username, I will have "Type mismatch. Required:String Found: EditText" type of error. Do I have to create a function to pass value from data class to RegisterFragment class in order to have proper param input. If yes then how? If no then what way to assign param input? Btw, the
val username = binding.text
is fetch from layout_fragmentregister (an EditText) which its id = text
The error you are getting is because
val username: String
is null
your data class doesn't do much besides pass data along. You should use an instance of it and a view model to pass data to another fragment. A typical domain data class looks like this
data class User(
val userID: String,
val userName: String,
val emailAddress: String,
val photoUrl: String,
val firstName: String,
val lastName: String,
val age: Int,
val score: Int,
val sex: Int,
val emailVerified:Boolean)
to create an instance of the data class create a private var to store that data.
private var currentUser = User(someFunctionThatReturnsUserData)// the data must match the model
for test purposes and better understanding you can create a function with a test user
fun generateTestUser():User{return User("TEST ID","TEST NAME","TEST EMAIL")}
you can decide how to display the data in currentUser by accessing any of the properties. i prefer to do this in the view model as it removes a lot of logic from the UI thread and doesn't get reset on config changes.
currentUser.userId
currentUser.userName
currentUser.emailAddress
and so on. Usually best practice to create a private and public variables. Private to manipulate data, public to allow views to see the data but not modify it.
in your view model
private val _uid: MutableLiveData<String> by lazy { MutableLiveData<String>() }
val uid:LiveData<String> //this returns a string of live data for the ui to observe changes on.
get() = _uid //get the value from the private val
fun getUserId(){_uid.value = "some string"}
these are just examples and probably wont work with a simple copy/paste but hopefully helps a little.
I need to pass this:
private lateinit var memes: MutableList<Memes>
which has this model:
class Memes (
#SerializedName("id") var id: Long,
#SerializedName("title") var title: String
)
from activity a to b.
I've altready seen couple "solutions" and none of them works!
This is the last that I've tried:
val extras = Bundle()
val memesArrayList = ArrayList(memes)
val i = Intent(context, GalleryShow::class.java)
i.putExtras(extras)
i.putStringArrayListExtra("list", memesArrayList)
(context as Activity).startActivityForResult(i, 777)
However, I get Type mismatch: inferred type is ArrayList<Memes!> but ArrayList<String!>? was expected on memesArrayList.
EDIT:
This is my latest attempt now:
In activity A inside recyclerview item:
val extras = Bundle()
extras.putString("gal", "animals")
extras.putString("query", "")
val i = Intent(context, GalleryShow::class.java)
i.putExtra("list", memes.toTypedArray())
i.putExtras(extras)
(context as Activity).startActivityForResult(i, 777)
and this is inside activity B:
private lateinit var memes: MutableList<Memes>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_gallery_show)
memes = (this?.intent?.extras?.get("list") as? Array<Memes>)?.asList()!!.toMutableList()
}
You can use simply intent.putExtra instead of worrying about which variant like put_____Extra to use.
When extracting the value, you can use intent.extras to get the Bundle and then you can use get() on the Bundle and cast to the appropriate type. This is easier than trying to figure out which intent.get____Extra function to use to extract it, since you will have to cast it anyway.
The below code works whether your data class is Serializeable or Parcelable. You don't need to use arrays, because ArrayLists themselves are Serializeable, but you do need to convert from MutableList to ArrayList.
// Packing and sending the data:
val i = Intent(context, GalleryShow::class.java)
i.putExtra("list", ArrayList(memes)) // where memes is your MutableList<Meme> property
startActivityForResult(i, 777)
// Unpacking the data in the other activity:
memes = intent.extras?.get("list") as MutableList<Meme>
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 2 years ago.
When I'm trying start new activity with this code im getting tihs error:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
class ApiConnection {
var baseApiURl = "http://localhost:8081"
var data = arrayListOf<User>()
fun connectApi(): ApiService {
val retrofit = Retrofit.Builder()
.baseUrl(baseApiURl)
.addConverterFactory(GsonConverterFactory.create())
.build()
return retrofit.create(ApiService::class.java)
}
fun loginRequest(
login: String,
password: String
){
val service = connectApi()
val call = service.login(login, password)
call.enqueue(object: Callback<UserResponse> {
override fun onFailure(call: Call<UserResponse>?, t: Throwable?) {
Log.v("retrofit", "call failed")
}
override fun onResponse(call: Call<UserResponse>?, response: Response<UserResponse>?) {
addDataToList(response)
}
})
}
fun addDataToList(response: Response<UserResponse>?) {
var mainActivity = MainActivity()
data.add(
User(
response!!.body()!!.idUser,
response!!.body()!!.login,
response!!.body()!!.password,
response!!.body()!!.name,
response!!.body()!!.surname,
response!!.body()!!.lastLogin
)
)
mainActivity.loginIntoServer(data)
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val api = ApiConnection()
button_checkIfThisAccountExit_mainActivity.setOnClickListener {
api.loginRequest(editText_login_activityMain.text.toString(), editText_password_mainActivity.text.toString())
}
}
fun loginIntoServer(data: ArrayList<User>) {
val context = this
val intent = Intent(context, Main2Activity::class.java)
startActivity(intent)
}
}
Any ideas what can make this error? I'm using retrofit to connect with my API and after parsing the result data and adding it to the list i would like to run new activity (something like a login system. After putting username/password I want change the activity).
Source of your problem is var mainActivity = MainActivity(), You never create instance of activity by invoking the constructor because that is the job of android system, see this
Correct solution to your problem would be LiveData.
but if you want to make your current code work as it is, you will have to pass a context object in your ApiConnection class,
fun addDataToList(context: Context, response: Response<UserResponse>?) {
data.add(
User(
response!!.body()!!.idUser,
response!!.body()!!.login,
response!!.body()!!.password,
response!!.body()!!.name,
response!!.body()!!.surname,
response!!.body()!!.lastLogin
)
)
val intent = Intent(context, Main2Activity::class.java)
context.startActivity(intent)
}
Also update the signature of loginRequest to accept a Context object from your activity, so that it look as following
fun loginRequest(context: Context, login: String, password: String)
And now call it from your activity as
api.loginRequest((this as Context), editText_login_activityMain.text.toString(), editText_password_mainActivity.text.toString())
How in your opinion this code should look?
Only corrently working code isnt enought for me if you know what I mean.
I also want to have the code which is looking good.
I have an activity which has a ViewModel and the activity observes a liveData of ViewModel
when I first start the activity, it works but when I return to it again, I need to be just like new but it since it's still observing the liveDatas , it shows me old data. what should I do? (oh and keep in mind that I can't create a new activity )
This is my Activity
class LoginActivity : AppCompatActivity() {
lateinit var idEditText: EditText
lateinit var createAccountTextView: TextView
lateinit var loginButton: Button
private lateinit var userRepository: UserRepository
private lateinit var loginActivityViewModel: LoginActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
userRepository = UserRepository(application)
loginActivityViewModel = ViewModelProviders.of(
this,
LoginActivityViewModelFactory(userRepository)
).get(LoginActivityViewModel::class.java)
idEditText = findViewById(R.id.idEditText)
loginButton = findViewById(R.id.enterButton)
createAccountTextView = findViewById(R.id.createAccountTextView)
loginButton.setOnClickListener {
val loginID = idEditText.text.toString()
loginActivityViewModel.userEntry(loginID)
}
createAccountTextView.setOnClickListener {
val createAccountIntent = Intent(this, CreateAccountActivity::class.java)
startActivity(createAccountIntent)
finish()
}
}
override fun onResume() {
super.onResume()
loginActivityViewModel.idEditTextMutableLiveData.observe(this, Observer {
idEditText.error = it
})
loginActivityViewModel.onSuccessMutableLiveData.observe(this, Observer {
val mainIntent = Intent(this, MainActivity::class.java)
mainIntent.putExtra(loginValue, UserUI(it.username, it.userId.toInt()))
startActivity(mainIntent)
})
}
}
The point of using Livedata is to get the latest update to a data source. If your data source is persistent it means the data returned will always be the same.
What you need to do is clear the datasource on application/activity start. Then you can populate the data source as you go.
Livedata will always return data as long as the data source has data.
A way around it is to avoid returning a livedata object altogether and just return the object itself.
I'm an beginner in kotlin and im trying to pass a context as a parameter, but isnt working...
these are my codes:
FUNCTION saveDatabase
private fun saveDatabase(context : Context){
val fightersName = Match(1, fighter1.toString(), fighter2.toString(),
minute.toInt(), round.toInt())
val db = DBContract(context)
db.insertData(fightersName)
}
CALLING THE FUNCTION
saveDatabase(context)
WARNING
Typemismatch
Required: Context
Found: Context?
This class is a fragment that extends of a Fragment()
your function requires a non null Context object, whereas you are calling it with a nullable and mutable Context object. If you are sure your context is not null, call
saveDatabase(context!!)
!! means that you vouch for the object to be non null
Or you can check your function for safety, then change your function to
private fun saveDatabase(context : Context?){
if(context != null){
val fightersName = Match(1, fighter1.toString(), fighter2.toString(),
minute.toInt(), round.toInt())
val db = DBContract(context)
db.insertData(fightersName)
}
}
The getContext method that you're accessing as the context property in Kotlin has a nullable type Context? - since it will return null when your Fragment isn't attached to an Activity.
One way to deal with the error is to first fetch its value, and perform a null check before you call your function:
val context = context
if (context != null) {
saveDatabase(context)
}
The same check using let, in two different forms:
context?.let { ctx -> saveDatabase(ctx) }
context?.let { saveDatabase(it) }
You can also use requireContext if you are absolutely sure that your Fragment is attached to an Activity - this returns a non-nullable Context, or throws an exception if there isn't one available.
saveDatabase(requireContext())
it so easy. Try as follow
private fun saveDatabase(context : Context?){
val fightersName = Match(1, fighter1.toString(), fighter2.toString(),
minute.toInt(), round.toInt())
val db = DBContract(context)
db.insertData(fightersName)
}
If you are new in Android with kotlin you will surely need an "always available" context. This is the way:
class App : Application() {
companion object {
lateinit var instance: App
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
then you just need to pass:
val db = DBContract(App.instance)
Be sure of modifying the manifest:
<application
android:name=".App"
...>