I create a data binding for my fragment.
// fragment_my
<layout ...>
<data>
<variable
name="myData"
type="com.example.myproject.MyData" />
</data>
...
</layout>
// MyFragment
class MyFragment : Fragment() {
private lateinit var binding: FragmentMyBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(
inflater, R.layout.fragment_my, container, false
)
Log.d(TAG, ${binding.myData.item})
return binding.root
}
}
// MyData
data class MyData(
var item: String = "123"
)
But when I run my project, it show the item not init correctly and it always return an optional nullable value.
You should bind the data before accessing/using it.
Try this:
binding = DataBindingUtil.inflate(
inflater, R.layout.fragment_my, container, false
)
binding.myData = MyData()
Log.d(TAG, ${binding.myData.item})
return binding.root
binding.myData = MyData() is missing in your code.
Source: https://developer.android.com/topic/libraries/data-binding/expressions#binding_data
Related
Here is the code :
class FirstFragment : Fragment() {
private var _binding: FragmentFirstBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentFirstBinding.inflate(inflater, container, false)
binding.btnOpen.setOnClickListener {
Navigation.findNavController(view).navigate(R.id.secondFragment)
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
At line: Navigation.findNavController(view).navigate(R.id.secondFragment) I'm getting error as Type mismatch required view found view?
My question is why we can't combine navcontroller with view binding?
And is there any resources to learn restrictions of using view binding
Instead of view use binding.root
Like this:
Navigation.findNavController(binding.root).navigate(R.id.secondFragment)
binding.root is reference to root view.
I would like to ask about ViewBinding.
The official
buildFeatures{
viewBinding = true
}
but it does not work.
Here is an example code.
Layout Code
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:clickable="true"
android:focusable="true"
android:tint="#color/white"
android:src="#drawable/ic_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
Kotlin Code
val view = inflater.inflate(R.layout.fragment_list, container, false)
view.floatingActionButton.setOnClickListener { }
Is there any solution to this problem?
I don't know why it is not working.
Android studio Vierson4.1.2
Gradle Viesion 6.5
You need to use as a parent in layout.xml
Example:- my_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="#dimen/_4sdp">
<TextView
android:id="#+id/scanner"
android:layout_width="#dimen/_200sdp"
android:layout_height="#dimen/_200sdp"
android:layout_centerInParent="true"
app:squaredFinder="true" />
</RelativeLayout>
</layout>
and in Kotlin.
val binding = DataBindingUtil.inflate(android.view.LayoutInflater.from(context), R.layout.my_layout, null, false);
This is how you enable view binding
private var _binding: FragmentListBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentListBinding.inflate(inflater, container, false)
binding.floatingActionButton.setOnClickListener { }
return binding.root
Fragments outlive their views. Make sure you clean up any references to the binding class instance in the fragment's onDestroyView() method.
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
For more details, read.
Try like this.
class BaseActivity : AppCompatActivity(){
private lateinit var binding: (YourActivityBinding)
super.onCreate(savedInstanceState){
binding = YourActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.floatingActionButton.setOnClickListener (View.OnClickListener{
})
}
}
Try this. In Your Gradle (Modul):
android{
.
.
.
buildFeatures {
dataBinding true
}
}
After sync, in Your XML File, try to push (ALT+Enter) on your layout parent and choice
Convert To Data Binding
Now in your Activity
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
}
}
Or in Your fragment:
class BlankFragment: Fragment() {
private lateinit var binding: FragmentBlankBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentBlankBinding.inflate(inflater)
return binding.root
}
from now, for binding view, you should write something like this:
binding.textView.text = "test"
Thank you for your wonderful answers.
I had forgot to create a binding class.
I was able to solve the problem successfully and the error has been resolved.
ADD Code
private var _binding: FragmentListBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = FragmentListBinding.inflate(inflater, container, false)
binding.floatingActionButton.setOnClickListener {
}
return binding.root
}
I am stuck here, help.
I've got the following code:
ProfileFragment:
#AndroidEntryPoint
class ProfileFragment : Fragment() {
private val profileViewModel: ProfileViewModel by viewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = FragmentProfileBinding.inflate(inflater, container, false)
binding.viewModel = profileViewModel
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}
}
ProfileViewModel:
class ProfileViewModel #ViewModelInject constructor(
#ApplicationContext private val context: Context,
private val profileRepository: ProfileRepository
) : ViewModel() {
fun getUser() {
....
}
}
fragment_profile.xml:
<data>
<variable
name="viewModel"
type="my.app.viewmodel.ProfileViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="match_parent"
android:layout_height="55dp"
android:onClick="#{()->viewModel.getUser()}" />
</LinearLayout>
The problem is that the onClick is never triggered no matter what I do and how I try.
However, as soon as I do it like this in ProfileFragment it works just fine:
binding.myButton.setOnClickListener {
profileViewModel.getUser()
}
Any ideas? Cause I am stuck here
I am not sure if this issue still exist or not
but if you are using data binding and hilt for dependency injection please add below lines in your fragment onViewCreated
binding.lifecycleOwner = this
binding.viewModel = profileViewModel
What fixed this problem for me was adding the fragment reference to itself in onCreateView:
binding.<fragment_name> = this
Like so:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.v(TAG, "onCreateView")
binding = FragmentSettingsBinding.inflate(inflater, container, false)
binding.settingsFragment = this <---
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}
In my app, i have a fragment that call a remote service for get user profile information and show it, and I've used DataBinding for show data.
This is my layout:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.myapp.ProfileViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{viewModel.profile.firstName+ ' '+ viewModel.profile.lastName}" />
<!-- Other textviews -->
</LinearLayout>
</layout>
this is ProfileViewModel class
class ProfileViewModel : ViewModel() {
#Inject
lateinit var profileRepository: ProfileRepository
private var _profile = MutableLiveData<Profile>()
val profile: LiveData<Profile>
get() = _profile
fun getProfile(token: String) {
profileRepository.profile(
token,
{
// success
_profile.value = it.value
},
{
//error
}
)
}
}
data class Profile(
firstName : String,
lastName : String,
// other fields
)
And this is fragment where profile should be showed:
class ProfileFragment : Fragment() {
private lateinit var binding: FragmentProfileBinding
private lateinit var viewModel: ProfileViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_profile,
container,
false
)
viewModel = activity?.run {
ViewModelProviders.of(this)[ProfileViewModel::class.java]
} ?: throw Exception("Invalid Activity")
binding.viewModel = viewModel
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.getProfile(
"aToken"
)
}
}
Now it happen that first time i open fragment, repository call service and get data correctly, but "null" is showed inside textviews. If i close fragment and i reopen it, edittext are populate correctly. What's wrong with my code?
Set binding.lifecycleOwner in your fragment class in order for updates in LiveData objects to be reflected in corresponding views. Your fragment class should look like this:
class ProfileFragment : Fragment() {
private lateinit var binding: FragmentProfileBinding
private lateinit var viewModel: ProfileViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_profile,
container,
false
)
viewModel = activity?.run {
ViewModelProviders.of(this)[ProfileViewModel::class.java]
} ?: throw Exception("Invalid Activity")
binding.viewModel = viewModel
//add lifecycleOwner
binding.lifecycleOwner = this
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.getProfile(
"aToken"
)
}
}
I've created a simple application that load a random joke from the API and shows it in the TextView. For learning reasons I've implemented the app with data binding. At first I created a ViewModel, that fetch data from the API
class RandomJokeViewModel: ViewModel() {
private val jokeService = RetrofitService()
fun getRandomJoke(): MutableLiveData<RandomJoke> ? {
Log.e("getRandomJokeData", "yes")
return jokeService.loadRandomJoke()
}
In my Fragment I call the method to load the jokes and update the view
class RandomJokeFragment : Fragment() {
private lateinit var binding: FragmentRandomJokeBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(layoutInflater, R.layout.fragment_random_joke, container, false)
binding.lifecycleOwner = this
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_random_joke, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
loadRandomJoke()
}
private fun loadRandomJoke() {
Log.e("getAndroidVersion", "yes")
val randomJokeViewModel =
ViewModelProviders.of(requireActivity()).get(RandomJokeViewModel::class.java)
randomJokeViewModel.getRandomJoke()?.observe(this, Observer<RandomJoke> { randomJoke ->
binding.randomJoke = randomJoke
Log.e("RandomJokeFragment", "This is your Joke: ${randomJoke.value}")
})
}
My data calls looks like this:
data class RandomJoke (
#SerializedName("categories") val categories: ArrayList<String>,
#SerializedName("created_at") val createdAt: String,
#SerializedName("icon_url") val iconUrl: String,
#SerializedName("id") val id: String,
#SerializedName("updated_at:") val updatedAt: String,
#SerializedName("url") val url: String,
#SerializedName("value") val value: String)
In my Fragment Layout I defined the data binding ressources like this
<data>
<variable
name="randomJoke" type="com.example.jokegenerator.data.remote.response.RandomJoke"
/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorPrimaryDark"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="1dp"
android:layout_marginStart="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="#{randomJoke.value}"
android:textColor="#ffff"
tools:text="Hello World"/>
</androidx.constraintlayout.widget.ConstraintLayout>
So when I run the app my TextView is empty. In Logcat and Debugger I verified, that the randomJoke response has data. Why is my view not updating after I get the data? What did I miss?
Not completely sure that this is the reason but:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(layoutInflater, R.layout.fragment_random_joke, container, false)
binding.lifecycleOwner = this
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_random_joke, container, false)
}
here you inflate your layout 2 times.
instead try like this:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// This will inflate your layout. You just have to return the root view.
binding = DataBindingUtil.inflate(layoutInflater, R.layout.fragment_random_joke, container, false)
binding.lifecycleOwner = this
return binding.root // Returning your root view
}
The databinding is probably mixing of the 2 layouts.
In your onCreateView() your are returning:
return inflater.inflate(R.layout.fragment_random_joke, container, false)
but you need to return:
return binding.root