How to solve error with View Binding in Kotlin - kotlin

Here I'm getting this error
package lk.ac.kln.mit.stu.mobileapplicationdevelopment.activities
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View.inflate
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import lk.ac.kln.mit.stu.mobileapplicationdevelopment.R
class ShoppingActivity : AppCompatActivity() {
val binding by lazy{
ActivityShoppingBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_shopping)
setContentView(binding.root)
val navController = findNavController(R.id.shoppingHostFragment)
binding.bottomNavigation.setupWithNavContoller(navController)
}
}
Anyone know the reason to fail this code?
I tried adding below lines to the gradel as well
buildFeatures {
viewBinding true
}

You can better use this, this way the binding can be set in the onCreate as before this it can not build UI components.
var binding: ActivityShoppingBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityShoppingBinding.inflate(layoutInflater)
binding?.let {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_shopping)
setContentView(binding.root)
val navController = findNavController(R.id.shoppingHostFragment)
binding.bottomNavigation.setupWithNavContoller(navController)
}
}

ActivityShoppingBinding is in red because it's not recognised by the IDE - usually this means you haven't imported it, so it doesn't know what it is. And you don't have an import line for that class, so that's probably your problem!
The easiest fix is just to put your cursor over the error and do Alt+Enter (or whatever) or click the lightbulb icon that appears, and the IDE will offer to import it for you. Once that binding class is imported it should work fine.
Also you're calling setContentView twice - that's pointless (you're inflating a layout then throwing it away immediately) and it can introduce bugs if you accidentally set things on a layout that isn't being displayed. You should only be calling it once, with binding.root in this case.
You might want to look at the recommended way of initialising view binding in Activities (and the Fragments section too if you're using them). Avoid using lazy for UI stuff, since it can only be assigned once you can run into issues (mostly with Fragments, but the example in that link uses lateinit too)

Related

Migrating from kotlin-android-extensions to newer view binding

So if you are getting anything like below
> Configure project :app
Warning: The 'kotlin-android-extensions' Gradle plugin is deprecated.
This probably means which means that using Kotlin synthetics for view binding is no longer supported / deprecated.
So below is the answer where you can clearly get understanding on how to get / identify your ViewBinding class related to your views.
In order to migrate to the newer way of binding you need to first remove the kotlin synthetics plugin which could have been added as below :
apply plugin: 'kotlin-android-extensions'
OR
plugins {
...
id 'kotlin-android-extensions'
}
after removing synthetic plugin from app gradle you need to remove the imports which could like either of below :
activity /fragment view : import
kotlinx.android.synthetic.main.<your_activity_view>.*
normal views :
import kotlinx.android.synthetic.main.<your_layout_view>.view.*
Now begins actual migration
You need to add below inside your app gradle
android {
...
buildFeatures {
viewBinding true
}
}
After this you need to add a binding property where your view is to be bound.
Below is an example
:
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.myTextView.text = "my text" //say your text view id is like : my_text_view"
}
Note : if your activity layout is activity_main.xml then your binding should be ActivityMainBinding
here you will find view binding example for fragment
here is official migration doc from google

Viewmodel SavedStateHandle data lost after process death

I am trying to use savedStateHandle in my ViewModel and test it. My simple case is that I'm just storing an Int value in the SavedStateHandle and trying to get it again after the process death. But its not working at all. I am using the following dependency
implementation 'androidx.activity:activity-ktx:1.2.0-alpha08'
implementation "androidx.fragment:fragment-ktx:1.3.0-alpha08"
Below is my Fragment which is having one button and one TextView. When I click the button, number 5 is stored in the savedStateHandle of the ViewModel and then the same was observed via the getLiveData method of the savedStateHandle and the number is displayed in the TextView. So, after process death, it should correctly restore the value of 5 and display it in the text view. Following is my fragment code
class FirstFragment : Fragment() {
private val viewModel by lazy{
ViewModelProvider(this).get(FirstFragmentVM::class.java)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button_first.setOnClickListener {
viewModel.rememberNumber(5)
}
observeViewModel()
}
private fun observeViewModel(){
viewModel.rememberedNumber.observe(viewLifecycleOwner){
textview_first.text = it.toString()
}
}
}
and the following is my ViewModel code
#ExperimentalCoroutinesApi
#FlowPreview
class FirstFragmentVM(savedStateHandle: SavedStateHandle) : ViewModel() {
companion object{
private const val KEY_NUMBER_TO_REMEMBER="number:to:remember"
}
private val savedState=savedStateHandle
val rememberedNumber=savedState.getLiveData<Int>(KEY_NUMBER_TO_REMEMBER)
fun rememberNumber(number:Int){
savedState.set(KEY_NUMBER_TO_REMEMBER,number)
}
}
When run this app and click the button, the number 5 is stored in the savedStateHandle and its displaying "5" correcly in the text view. But when I put the app in the background and kill the process using adb and then restart the process from the recent screen, the entire app is restarted and its not showing the remembered number in the textView. Instead, its showing "Hello" which I set in the layout xml. I am killing the process as follows
adb shell
am kill <package name>
Anyone kindly help me why its not at all working for me? What am I doing wrong here?
Finally, I found the reason. The app restarts if we kill the app process after launching it directly from the IDE. Instead, If I close the app by swiping it from recents, launch it from the app drawer and then killing it via adb works and restores the state of the app perfectly.

Preventing code in subscribe from executing when subject is initialized

In this code:
class RequestNewPasswordFragment {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnRequestNewPassword.setOnClickListener {
view.hideKeyboard()
viewModel.validateEmail(txtInputLayoutEmail.textValue)
}
disposables += viewModel.emailValidationSubject
.observeOnMainThread()
.subscribe { validationResponse ->
viewModel.requestNewPassword()
}
}
When the fragment is initialized, the emailValidationSubject gets initialized. This causes the code in subscribe to execute, which makes a call to the requestNewPassword in the viewModel. I want to avoid this. I want this to only be called when btnRequestNewPassword is clicked. The code in subscribe should only get called when the viewModel needs to validate the input. How can I prevent viewModel.requestNewPassword() from being called when the fragment is initialized?
I'm assuming your emailValidationSubject is a BehaviourSubject based on your previous question here.
BehaviourSubject will always emit an value on subscription, hence you need to provide an initial value.
it begins by emitting the item most recently emitted by the source Observable (or a seed/default value if none has yet been emitted)
You need to use a PublishSubject:
PublishSubject emits to an observer only those items that are emitted by the source Observable(s) subsequent to the time of the subscription.

I am using android studio and the mainactivity class is .kt type. I keep having this error: Expecting member declaration

I am using android studio and I have some issues with this piece of code. Could someone tell me what I am doing wrong?
The system keeps telling me "Expecting member declaration."
This is the code
class MainActivity: AppCompatActivity() {
class TextView totalTextView; //This is where I am having the error
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
I am assuming you are trying to create a totalTextView variable. In kotlin you can declare a variable like this
lateinit var totalTextView : TotalTextView
This is not right way to create or define object of TextView.
1 > var totalTextView : TextView ? = null
1 > lateint var totalTextView : TextView
In kotlin, No need to define id of ViewGroup items , avoid to define the instance of view item when you are using kotlin

How to declare variables in Android (Kotlin) using Google code style?

I started to build the app in Kotlin and I want to know how to correctly initialize variables. For example in Java it was like:
private TextView mSomeTextView;
And then we call findViewById in some methods. But in Kotlin I can't just write something like that, I need to:
private val textView: TextView = findViewById(R.id.text)
I write it under onCreate as I used to. Question: is it right place for it? If no -- where and how should I do it?
You should use lateinit:
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
...
textView = findViewById(R.id.text)
}