I have the following code for my layout and my fragment:
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ScanFragment">
<SurfaceView
android:id="#+id/cameraPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</layout>
class ScanFragment : Fragment() {
private lateinit var binding: FragmentScanBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
FragmentScanBinding.inflate(inflater, container, false).also {
binding = it
}.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.cameraPreview
}
}
However, I can't seem to access the cameraPreview view directly from the binding. I have already added the pre-requisites for DataBinding below:
dataBinding {
enabled = true
}
and
android.databinding.enableV2=true
inside the gradle.properties
You should use DataBindingUtil
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_scan, container, false)
return binding.root
Related
I have an app that uses NavigationComponents among with ViewPager 2.
I'd like to use ViewPager for switching between fragments. I've made my ActivityMain as FragmentContainerView, and I'd like to have ViewPager implemented in one of my fragments.
The problem is, the ViewPager doesn't work at all. It doesn't change fragments, don't know why. What should I change in the code?
ActivityMain
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
First fragment
class BlankFragment : Fragment() {
private var mPag: ViewPager2? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_blank, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mPag = view.findViewById(R.id.pager123)
val adapter = PagerAdapter(this)
val list = mutableListOf<Fragment>()
list.add(BlankFragment())
list.add(BlankFragment2())
mPag?.adapter = adapter
}
}
.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BlankFragment"
android:orientation="vertical">
<TextView
android:id="#+id/test123"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="111111111" />
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/pager123"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#a1a1"/>
</LinearLayout>
Second fragment
class BlankFragment2 : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank2, container, false)
}
}
.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BlankFragment2">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="222222222222222" />
</FrameLayout>
Nav graph
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_graph"
app:startDestination="#id/blankFragment">
<fragment
android:id="#+id/blankFragment"
android:name="com.example.myapplication.BlankFragment"
android:label="fragment_blank"
tools:layout="#layout/fragment_blank" />
<fragment
android:id="#+id/blankFragment2"
android:name="com.example.myapplication.BlankFragment2"
android:label="fragment_blank2"
tools:layout="#layout/fragment_blank2" />
</navigation>
Pager adapter
class PagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
val mFragments = mutableListOf<Fragment>()
override fun getItemCount(): Int {
return mFragments.size
}
override fun createFragment(position: Int): Fragment {
when (position){
0 -> return BlankFragment()
1 -> return BlankFragment2()
}
return mFragments[position]
}
}
What you are essentially missing is what I tried to explain in the previous question.
You need to use Navigation component and ViewPager2 as two separate entities.
There is also issue with your PagerAdapter. Fragment adapters are not similar to other Adapters you might have experience with(mFragments).
They shouldn't hold a reference to those fragments. There are two main Fragment adapters(more here).
class PagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment {
return when (position){
0 -> BlankFragment1()
1 -> BlankFragment2()
else -> throw IllegalArgumentException("Out of fragments, will depend on getItemCount")
}
}
}
You need to have Fragments that are solely in the Navigation component(e.g. FirstFragment & SecondFragment) and Fragments that belong to the ViewPager2(e.g. BlankFragment1 & BlankFragment2)
<?xml version="1.0" encoding="utf-8"?>
<navigation android:id="#+id/nav_graph"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="#id/FirstFragment">
<fragment
android:id="#+id/FirstFragment"
android:name="com.example.myapplication.FirstFragment"
android:label="#string/first_fragment_label"
tools:layout="#layout/fragment_first" >
<action
android:id="#+id/action_FirstFragment_to_SecondFragment"
app:destination="#id/SecondFragment" />
</fragment>
<fragment
android:id="#+id/SecondFragment"
android:name="com.example.myapplication.SecondFragment"
android:label="#string/second_fragment_label"
tools:layout="#layout/fragment_second" />
</navigation>
Fragments for ViewPager2:
class BlankFragment1: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_blank_1, container, false)
}
}
class BlankFragment2: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_blank_2, container, false)
}
}
The FirstFragment is going to host the ViewPager2 that will allow you to swipe between BlankFragment1 and BlankFragment2.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_first, container, false)
view.findViewById<ViewPager2>(R.id.vp2)?.let { viewPager2 ->
val pagerAdapter = PagerAdapter(this)
viewPager2.adapter = pagerAdapter
}
return view
}
Now the only thing you need to do in any of the fragments to use Navigation component is to simply findNavControler and navigate to destination you want.
class BlankFragment1: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_blank_1, container, false)
view.findViewById<MaterialButton>(R.id.blank_1_button).setOnClickListener {
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}
return view
}
}
I'm trying to use Data binding to run a function when an onClick event occurs, I'm hoping someone can tell me what I'm doing wrong here.
The log item in myClick doesn't run.
XML
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="myBinding"
type="com.example.deletebindingtest.MyFragment" />
</data>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MyFragment"
android:orientation="vertical">
<Button
android:id="#+id/my_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/button"
android:onClick="#{(view) -> myBinding.myClick()}"/>
</androidx.appcompat.widget.LinearLayoutCompat>
</layout>
My Fragment
class MyFragment : Fragment() {
private lateinit var binding: FragmentMyBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_my,
container,
false
)
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}
fun myClick() {
Log.i("TEST", "Its working")
}
}
When I click on the extension in the XML it takes me to myClick function.
a Quick fix is on onCreateView add binding.myBinding=this
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_my,
container,
false
)
binding.lifecycleOwner = viewLifecycleOwner
binding.myBinding=this // here
return binding.root
}
also I prefer listener binding you can check it here
I went through couple of examples but either they have too many abstractions or out of date tutorials. I would like to implement this feature in the minimum possible way. I need help with the UserForm class here with User passed as arguments in a Navigational Architecture.
Module app build.gradle I've added this
android {
...
dataBinding {
enabled = true
}
}
User class
#Parcelize
data class User(var first: String, var last: String): Parcelable
FormFragment.kt
class UserForm: Fragment() {
private val user by lazy {
arguments?.getParcelable("user") ?: User("John", "Doe")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_form, container, false)
}
}
fragment_form.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">
<data>
<variable name="user" type="data.User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="#+id/first"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="text"
android:text="#{user.first}" />
<EditText
android:id="#+id/last"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="text"
android:text="#{user.last}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Some snippets
modify your code below:
FormFragment.kt
class UserForm: Fragment() {
private val user by lazy {
arguments?.getParcelable("user") ?: User("John", "Doe")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = FragmentFormBinding.inflate(inflater, container, false).apply {
user = this#UserForm.user
}
return binding.root
}
}
Explanation:
Because you used DataBinding in fragment_form.xml, the corresponding FragmentFormBinding class will be automatically generated
We usually use XxxBinding.inflate instead of inflater.inflate (layoutid, container, false)
After FragmentFormBinding.inflate, we bind the user object to fragment_form.xml
First Fragment's code
class MainFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val mainInflater = inflater.inflate(R.layout.fragment_main, container, false)
return mainInflater
}
fun thisdata():String{
return "Hello from MainFragment"
}
}
First Fragment XML
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainFragment">
<EditText
android:id="#+id/etSavedData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="60dp"
android:hint="Enter the Text"/>
<Button
android:id="#+id/btnSaveData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="120dp"
android:text="Save"/>
</RelativeLayout>
Main Activity's code
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Set replace Main Activity content with the Fragment1 content
val mainFragment = MainFragment()
supportFragmentManager.beginTransaction().add(R.id.contain_fragment, mainFragment).commit()
val thedata = mainFragment.thisdata()
Log.e("Main Frag to Activity", thedata)
btnSaveData.setOnClickListener {
val secondFragment = SecondFragment()
supportFragmentManager.beginTransaction().add(R.id.contain_fragment, secondFragment).commit()
}
}
}
Main Activity XML
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/contain_fragment"
tools:context=".MainActivity">
</RelativeLayout>
Second Fragment's code
class SecondFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val secondInflater = inflater.inflate(R.layout.fragment_second, container, false)
return secondInflater
}
}
Second Fragment XML
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/holo_blue_dark"
tools:context=".SecondFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="#+id/tvDisplayText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Text displayed here." />
</FrameLayout>
Following Exception showing up in Logcat
Caused by: java.lang.NullPointerException: Attempt to invoke virtual
method 'void
android.widget.Button.setOnClickListener(android.view.View$OnClickListener)'
on a null object reference
at com.example.demo2.MainActivity.onCreate(MainActivity.kt:22)*
You either need to move your btnSaveData button into the MainActivity, or you should move the onClickListener into the MainFragment, along with some other changes. Something like:
class MainFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val mainInflater = inflater.inflate(R.layout.fragment_main, container, false)
btnSaveData.setOnClickListener {
val secondFragment = SecondFragment()
activity.supportFragmentManager.beginTransaction().add(R.id.contain_fragment, secondFragment).commit()
}
return mainInflater
}
fun thisdata():String{
return "Hello from MainFragment"
}
}
**Thanks Tash, that actually make sense, unfortunately it was still showing the same exception but I finally manage to resolve the issue by doing the following. **
class MainFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val mainInflater = inflater.inflate(R.layout.fragment_main, container, false)
mainInflater.btnSaveData.setOnClickListener {
val secondFragment = SecondFragment()
secondFragment?.let {
activity?.supportFragmentManager?.beginTransaction()?.replace(R.id.contain_fragment, secondFragment)?.commit()
}
}
return mainInflater
}
fun thisdata():String{
return "Hello from MainFragment"
}
}
I am using a fragment within an activity and using data binding to bind its layout for both the activity and fragment as shown below:
activity.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#id/FAB_fromHomeActivity_BottomAppBarAttached"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_add_dark"
android:backgroundTint="#color/colorAccent"
app:layout_anchor="#id/BottomAppBar_fromHomeActivity_Main"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
activity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity)
}
fragment.kt
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_goals, container, false)
return mBinding.root
}
Is there any way to access the FAB in the activity using the binding in the fragment ? (For example something like that mBinding.parent.FAB)
I can't find any information on this. Can anyone help ?