Here's the code:
private lateinit var binding: ResultProfileBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.**inflate(layoutInflater)**
val view = binding.root
setContentView(view)
}
Why layoutInflater? Isn't inflate supposed to inflate an xml file?
Doing a Ctr+Q on inflate gives this public static #NonNull com.example.ActivityMainBinding inflate(#NonNull android.view.LayoutInflater inflater)
I can't find this function on the android developer website. It's either in LayoutInflater or View. Where can you find this function?
Isn't inflate supposed to inflate an xml file?
Yes. A LayoutInflater inflates layout resources.
I can't find this function on the android developer website
It is code-generated in your project. That generated code is not significantly different than if you had typed it in yourself, which is why it uses a LayoutInflater to inflate the associated layout resource.
Where can you find this function?
On your computer. Specifically, it will be in one of the subdirectories off of your module's build/ directory.
Related
For example java code
public abstract class BindingElement<T extends ViewDataBinding> {
T binding;
abstract public T createBinding(LayoutInflater inflater, ViewGroup parent);
public BindingElement(ViewGroup parent) {
binding = createBinding(LayoutInflater.from(parent.getContext()), parent);
binding.setLifecycleOwner(ViewTreeLifecycleOwner.get(parent));
}
}
I need some necessary property that defined in constructor. And then i will do something with that property. What is the best way write it in kotlin?
This doesn’t directly answer your question but provides a safer alternative.
You should avoid calling an open or abstract function from the constructor in Java or Kotlin, even though it’s allowed. It is fragile and can cause weird bugs that are difficult to resolve. Read here: In Java, is there a legitimate reason to call a non-final method from a class constructor?
An alternative in this case would be to make your function into a constructor parameter. Your class doesn’t even need to be open or abstract to support this.
class ViewBindingParameter<T: ViewBindingData> (
parent: ViewGroup,
inflateBinding: (LayoutInflater, ViewGroup)->T
) {
val binding: T = inflateBinding(LayoutInflater.from(parent.context), parent)
}
Usage:
val bindingParam = ViewBindingParameter(parent, SomeBinding::inflate)
If you aren't planning to add features to this class, you might as well just use a function that directly returns a binding so you don't have to deal with the wrapper class. Maybe an extension function of the parent view:
fun <T: ViewBindingData> ViewGroup.inflateChildBinding(inflateBinding: (LayoutInflater, ViewGroup)->T): T =
inflateBinding(LayoutInflater.from(context), this)
and use it like:
val binding = parent.inflateChildBinding(SomeBinding::inflate)
Kotlin is no different from Java in case of abstractions, so I assume something like below will work
abstract class BindingElement<T: ViewDataBinding> {
val binding: T
abstract fun createBinding(LayoutInflater inflater, ViewGroup parent): T
init {
binding = createBinding(...)
}
}
UPD: I noticed that your method requires field provided in constructor, so instead of init block you will use
constructor(parent: ViewGroup) {
binding = createBinding(...)
}
It may look more like Kotlin code
abstract class BindingElement<T: ViewDataBinding>(
val parent: ViewGroup
) {
val binding = createBinding(..., parent)
abstract fun createBinding(LayoutInflater inflater, ViewGroup parent): T
}
And this code is calling the non-final function in the constructor which is an unsafe operation.
This Android tutorial introduces the concept of view binding, with this section demonstrating how to use it. In this case, the view binding is set up using the following code.
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
The explanation for the call to ActivityMainBinding.inflate() is as follows:
This line initializes the binding object which you'll use to access
Views in the activity_main.xml layout.
What this does not explain is where the variable layoutInflater is defined.
When using Android Studio, the code completion suggests that the variable "comes from getLayoutInflater()":
getLayoutInflater() seems to be a method in Activity, but this doesn't help me understand what the reference to layoutInflater is doing, where it is defined, and how it is in scope at this point of the code. Can someone help me to understand this please?
ActivityMainBinding.java is the generated class by data binding which has a static method inflate(). When you pass the layoutInflater(it retrieve a standard LayoutInflater instance that is already hooked up to the current context) to inflate() it generates the same code under the code as we usually do while inflating the views and it fetches the layout name automatically.
So, the whole method is like
public static ActivityMainBinding inflate(#NonNull LayoutInflater inflater,
#Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.activity_main, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
I hope this is what you are looking and sure can help you. Thanks
I came at the same question and found layoutInflater was declared in NavigationMenuPresenter.java.
package com.google.android.material.internal;
...
public class NavigationMenuPresenter implements MenuPresenter {
...
LayoutInflater layoutInflater;
...
public void initForMenu(#NonNull Context context, #NonNull MenuBuilder menu) {
layoutInflater = LayoutInflater.from(context);
...
In my case, full path to the java script was
%HOMEDRIVE%%HOMEPATH%\.gradle\caches\modules-2\files-2.1\com.google.android.material\material\1.7.0\289bbb3a7fea52532f1163487f9469217ee608a9\material-1.7.0-sources.jar!\com\google\android\material\internal\NavigationMenuPresenter.java
After a little digging I found out that the getLayoutInflater comes from the Activity class. Activity class is parent of androidx.core.app.ComponentActivity that is parent of ComponentActivity that is parent of FragmentActivity that is parent of AppCompatActivity that is parent of your Activity class.
Im creating a really basic recyclerview in fragment_home.xml which is linked to a <FrameLayout /> inserted in activity_main.xml
It works well...until I create the HomeFragment class in HomeFragment.kt, which inflates fragment_home.xml
Then I want to add up the arrayList (arrayHomeMenu) in the function onActivityCreated()
my problem is with this line
val homeMenuAdapter = HomeMenuAdapter(arrayHomeMenu, this) //context "this" appears with red underline
It retrieves error, so I cant continue...
HomeFragment.kt
class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View?{
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?){
super.onActivityCreated(savedInstanceState)
//create list of words
val arrayHomeMenu = ArrayList<HomeMenuModel>()
arrayHomeMenu.add(HomeMenuModel("Verbs","List of 461 words", R.drawable.ic_logo))
arrayHomeMenu.add(HomeMenuModel("Nouns","List of 52 words", R.drawable.ic_logo))
arrayHomeMenu.add(HomeMenuModel("Adjectives","List of 65 words", R.drawable.ic_logo))
arrayHomeMenu.add(HomeMenuModel("Adverbs","List of 345 words", R.drawable.ic_logo))
val homeMenuAdapter = HomeMenuAdapter(arrayHomeMenu, this) //context "this" appears with red underline
homeMenu_recyclerView.layoutManager = LinearLayoutManager(context)
homeMenu_recyclerView.adapter = homeMenuAdapter
}
This is a context problem....
I tried to replace this by activity, context, Context, applicationContext with not success at all...
HomeMenuAdapter.kt
class HomeMenuAdapter(val arrayList: ArrayList<HomeMenuModel>, val context: Context) :
RecyclerView.Adapter<HomeMenuAdapter.ViewHolder>() {
....//correct content
}
It works correctly when I create the recyclerview direct in activity_main.xml and work directly with MainActivity.kt
Once I move the recyclerview to a fragment...it throws the error described above.
HomeMenuModel.kt
class HomeMenuModel(val hm_title:String,val hm_description: String, val hm_image:Int)
I was checking this answer, but no success...
what am doing wrong?? thank you
The reason why you can use this as context inside an Activity, is because Activity extends Context.
Fragment however does not extend Context so you cannot use this.
The reason why activity, context also don't work is Kotlin's distinction of nullable types. While activity and context do return a Context, the return value is nullable. You can see this by paying close attention to the error message that appears when hovering over the red underline:
Type mismatch.
Required:
Context
Found:
Context? (or FragmentActivity?)
The question mark indicates that this is a nullable type, while a non-nullable Context is required. The reason why they're nullable is that the Fragment can only retrieve the Activity when it is attached to it, which is not always the case.
However, Fragment has a convenient method called requireContext() to work around this issue. It has a non-nullable return type but will instead throw an exception if it cannot retrieve the context, so it is on you to make sure to only call it when the Fragment is attached.
In short, you should be able to instantiate your adapter like this:
val homeMenuAdapter = HomeMenuAdapter(arrayHomeMenu, requireContext())
Since you are in a Fragment this can't be passed as a context:
val homeMenuAdapter = HomeMenuAdapter(arrayHomeMenu, this)
In the above code you are trying to pass fragment as a context, so instead what you can do is get the context of the fragment. So you can do:
val homeMenuAdapter = HomeMenuAdapter(arrayHomeMenu, requireContext())
I have an architectural question about the android ViewModels:
Let's say that in my App I have an Activity with two Fragments inside (using a Viewpager). The two fragments do different things (therefore may have their own ViewModel?), but they also both need various data that is similar.
This is for example the state if a network connection is available or not (and both fragments show different error UIs if there is no connection), or some user setting that comes via a Push from a server and affects both fragments equally.
This looks something like this:
Now my question is how to deal with that situation when using ViewModels?
Is it good that a view observes multiple ViewModels, like it would be if I have a ViewModel for the Activity (holding the state that both need equally) and one for each fragment, like this:
This was hinted here for example, but it is not a good practice, as the relationship in MVVM generally is
View n - 1 ViewModel n - 1 Model
But I am not sure where the right place for such shared LiveData is in my case?
Late answer but I asked myself the same question and found the answer in Google guide.
Especially for fragments, it is mentioned on Google Documentations explicitly here
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
// Update the UI
})
}
}
I think the concept behind the ViewModel was that it is supposed to be related to a single "Screen" rather than a "View". So going by that logic, I think you can use the same ViewModel if multiple fragments reference the same ViewModel because they technically belong to the same "Screen".
In the fragments, you could request the activity for the ViewModel which holds the instance of LiveData and could give you the updates as needed.
Hope this answers your question.
Update: I found a link to a sample fragment in Google samples. Check out onCreateView() method. Pasting code below for reference:
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View root = inflater.inflate(R.layout.addtask_frag, container, false);
if (mViewDataBinding == null) {
mViewDataBinding = AddtaskFragBinding.bind(root);
}
mViewModel = AddEditTaskActivity.obtainViewModel(getActivity());
mViewDataBinding.setViewmodel(mViewModel);
setHasOptionsMenu(true);
setRetainInstance(false);
return mViewDataBinding.getRoot();
}
P.S. If you have found a better solution/answer/practice, lemme know.
I have been trying to learn through following YouTube tutorials. I am using Android Studio 3.1 Canary and I get to the same point in the tutorials and get stuck. For instance if you go to this YouTube tutorial https://youtu.be/3RMboPhUbmg?t=210 at the 3:30 min mark.
When they are inputting the MaterialSearchView searchView; it shows up for me with a red underline saying "expecting member declaration" and no matter how many searches I try I cannot find an answer. What is the solution to this error? Thanks
This is the code contained in the Main2Activity.kt. The result should be calling or knowing the toolbar and materialsearchview objects
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v7.widget.Toolbar
import com.miguelcatalan.materialsearchview.MaterialSearchView
import kotlinx.android.synthetic.main.activity_main2.*
class Main2Activity : AppCompatActivity () {
**MaterialSearchView searchView;** "expecting member declaration error"
**Toolbar toolbar;** "expecting member declaration error"
Toolbar toolbar = (Toolbar) view.findViewById(R.id.toolbar);
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
toolbar=(Toolbar()) findViewbyId(R.id.toolbar);
setSupportActionBar(toolbar);
}
*private void searchViewCode()
{
searchView=(MaterialSearchView)findViewById(R.id.search_view);
}
}
1) Understand your language syntax
Your tutorial is in Java. You try to write in Kotlin. Java and Kotlin have different syntax and if you reproduce this tutorial word for word in Kotlin, it will fail.
Follow the turorial and write your code in Java. Switch to Kotlin later when you're more confident with what you're doing. Focus on one thing at a time.
2) Find views at the right time
The Activity object is instatiated for you by the framework with a public empty constructor. At this time there are no views to be found. If you call findViewById any time before setContentView it will return null and crash if you try to assign it to a non-nullable variable.
The above applies for both Java and Kotlin.
For Java follow the tutorial. It should look something like this:
Toolbar toolbar; // Declare the variable here so it's accessible outside of onCreate.
#Override
public void onCreate(#Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2); // Inflate view hierarchy.
toolbar = (Toolbar) view.findViewById(R.id.toolbar); // Find your views.
setSupportActionBar(toolbar);
}
In Kotlin there are several options.
You can use lateinit modifier which allows you to declare a non-nullable variable but assign it later in onCreate.
lateinit var toolbar: Toolbar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
toolbar = findViewById(R.id.toolbar) as Toolbar
setSupportActionBar(toolbar)
}
Or you can use lazy delegate. The variable will be assigned when you first access it.
val toolbar: Toolbar by lazy(LayzThreadSafetyMode.NONE) {
toolbar = findViewById(R.id.toolbar) as Toolbar
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
setSupportActionBar(toolbar)
}
Don't use the delegate. It creates an unnecessary holder object for each lazy, that's wasteful.
You can also use Kotlin Android Extensions and just access toolbar directly because all of the heavy lifting is done for you.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
setSupportActionBar(toolbar)
}
Alternatively you can use View Binding available since Android Studio 3.6 Canary 11.
private lateinit var binding: ActivityMain2Binding
#Override
fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
binding = ActivityMain2Binding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
}
You get high performance of the first option and strong type safety on top.
It seems you are declaring your searchView and toolbar variables using Java syntax, and not the Kotlin syntax, so the compiler does not understand what you are declaring.
So change the declaration to:
var searchView: MaterialSearchView? = null
val toolbar: Toolbar = view.findViewById(R.id.toolbar) as Toolbar
or if you are using Kotlin Android extensions you should be able to retrieve the toolbar like so (using the view id directly):
In my case, the problem was a invisible char due copy and paste from .PDF
Note that there are two chars between '{' and 'onClick' in the snippet below.
setOnClickListener { onClick(item) }
This is the cause of my nightmares.
The main problem is the syntax, Whenever upgrade 3 series to 4. Java syntax transform to Kotlin syntax.
Kotlin Syntax:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
}
}
Java Syntax:
#Override
public void onCreate(#Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2); // Inflate view hierarchy.
}
}
If you want to change Kotlin syntax to Java Syntax.
Came back to mainActivity programming panel after that
TOOLS -> KOTLIN -> Show KOTLIN Bytecodes
Then Check on the JVM 8 target and Tick that.
After that invalid catch/Restart
Problem has been resolved.