So, I am trying to migrate from kotlin synthetic to Jetpack view binding.
Here is the kotlin synthetic code (works fine) that simply set visibility to invisible of TextView in the parent activity from fragment.
import kotlinx.android.synthetic.main.activity_main.*
class FirstFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_first, container, false)
requireActivity().textView.visibility = View.INVISIBLE
return view
}
}
And here is what I'm doing to migrate:
import com.mypc.myapp.databinding.FragmentFirstBinding
import com.mypc.myapp.databinding.ActivityMainBinding
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)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.textView.visibility = View.INVISIBLE
binding.textview.setOnClickListener {
Navigation.findNavController(view).navigate(R.id.goto_secondfragment)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
I'm getting error as 'Unsolved reference' at 'textview':
binding.textView.visibility = View.INVISIBLE
And at:
binding.textview.setOnClickListener {
Navigation.findNavController(view).navigate(R.id.goto_secondfragment)
}
Obviously the compiler is not able to find TextView that is in Activity
I've added this line:
import com.mypc.myapp.databinding.ActivityMainBinding
Since your binding is private to MainActivity you can refer to your textView from the MainActivity only. To show/hide this view from FirstFragment you can create a public function in MainActivity and call it from your FirstFragment.
class MainActivity: AppCompatActivity {
private var _binding: ActivityMainBinding? = null
private val binding get() = _binding!!
fun showHideTextView(visible: Boolean) {
binding.textView.isVisible = visible
}
}
And in your fragment, you can call:
(requireActivity() as MainActivity).showHideTextView(false) // This will hide the textView
First of all, you should define instance of activity view binding in baseActivity which is a parent class of your MainActivity, and then define method to change your text view like 'showTextView' , after that in the base fragment class initalize base activity instance with casting context object in onAttach method.
I provide you some code:
abstract class BaseRegisterActivity : BaseActivity() {
//---
protected lateinit var binding: ActivityRegisterBinding
private val navHostFragment by lazy {
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as
NavHostFragment
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_register)
//---
}
fun showTextView() {
binding.textView.visibility = View.VISIBLE
binding.textView.setOnClickListener {
val fragment =
navHostFragment.childFragmentManager.fragments[0]
if (fragment is FirstFragment) {
//todo
}
}
}
}
abstract class BaseFragment : Fragment(), Injectable {
//--
lateinit var baseActivity: BaseRegisterActivity
override fun onAttach(context: Context) {
super.onAttach(context)
baseActivity = context as BaseRegisterActivity
}
//--
}
class ShopFragment : Fragment() {
var binding:FragmentShopBinding ?= null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentShopBinding.inflate(inflater,container,false)
return binding?.root
}
Related
I used before view Binding to bind layout with activity or fragment, I saw that after Fragment 1.1.0 we can bind layout in constructor, which is performance. What android says.
Activity
With binding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
With constructor
class MainActivity : AppCompatActivity(R.layout.activity_main)
Fragment
With binding
class MyFragment: Fragment() {
private var _binding: MyFragmentBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = MyFragmentBinding.inflate(inflater, container, false)
return binding.root
}
}
With constructor
class MyFragment: Fragment(R.layout.my_fragment) {
private var _binding: MyFragmentBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = MyFragmentBinding.bind(view)
}
}
I am working on a basic bluetooth app in android studio and I am having trouble with view binding. So far I have this in my MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
...
Later in the onCreate I have this line
val btnONOFF = findViewById<View>(R.id.btnONOFF) as Button
and I would like to replace the findViewById using viewbinding but I'm not sure what to replace that line with.
The android developers website says to use something like this
binding.name.text = viewModel.name
but I'm also not sure what a viewModel is.
Here is my ViewFragment.kt incase that is helpful
class ViewFragment : Fragment() {
private var _binding: ActivityMainBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): RelativeLayout {
_binding = ActivityMainBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}}
Any help is appreciated, thanks!
You can find the tutorial of viewmodel from here.
ViewFragment.kt
class ViewFragment : Fragment() {
private var _binding: ActivityMainBinding? = null
private val binding get() = _binding!!
private val viewModel by viewModel<ActivityViewModel>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): RelativeLayout {
_binding = ActivityMainBinding.inflate(inflater, container, false)
setupView()
return binding.root
}
private fun setupView(){
binding.text = viewModel.name
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
You can customise your ActivityViewModel.kt
class ActivityViewModel : ViewModel() {
val name = "Hello"
}
Note: by viewModel is koin injection.
I have a Parent Fragment in an activity and have a Child Fragment in Parent Fragment. The NavGraphs of the two Fragments are different. I want to access the function in the Parent Fragment from the Child Fragment, but the application crashes. How can I do it?
Child Fragment:
class ProfilinMenu : Fragment(){
private var _binding : FragmentProfilinMenuBinding? = null
private val profilinmenubinding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View {
_binding = FragmentProfilinMenuBinding.inflate(inflater,container,false)
(parentFragment as Parent).changeName() // this is for access parent
return profilinmenubinding.root
}}
Parent Fragment:
public class Parent : Fragment() {
private var _binding : FragmentParentBinding? = null
private val parentBinding get() = _binding!!
private var instance: Parent? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentParentBinding.inflate(inflater, container, false)
val view = parentBinding.root
return view
}
public fun changeName(){
parentBinding.profilinIsmin.text = "Merhaba"
}
Try this..
NavHostFragment navHostFragment = (NavHostFragment) getParentFragment();
ParentFragment parent = (ParentFragment) navHostFragment.getParentFragment();
parent.changeName()
To use viewbinding in an android app I am basically creating base classes for Activity & Fragment to remove boilerplate of everytime writing inflating code.
ACTIVITY:
BaseActivity with viewbinding:
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = getViewBinding()
}
abstract fun getViewBinding(): VB
}
MainActivity:
class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
//we can directly use binding now and it works fine inside activity
//binding.view.doSomething()
}
override fun getViewBinding(): ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater)
}
FRAGMENTS :
BaseFragment:
abstract class BaseFragment<VB : ViewBinding> : Fragment() {
var binding: VB? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = getViewBinding(view)
}
abstract fun getViewBinding(view: View): VB
}
DemoFragment:
class DemoFragment : BaseFragment<DemoFragmentBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//problem is here
binding.txtData.text="Something"
}
override fun getViewBinding(view: View): DemoFragmentBinding = DemoFragmentBinding.bind(view)
}
demo_fragment.xml:
<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=".ui.fragments.DemoFragment">
<TextView
android:id="#+id/txt_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello" />
</FrameLayout>
Problem : Unable to access views using binding inside Demofragment. I don't know why it works with activity and not with fragment.
2nd way that I don't want todo:
implementation 'androidx.fragment:fragment-ktx:1.3.1'
class DemoFragment : Fragment(R.layout.demo_fragment) {
lateinit var binding: DemoFragmentBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = DemoFragmentBinding.bind(view).apply {
txtData.text = "Hello World"
}
}
}
You need to override onCreateView in BaseFragment and initialize the viewbinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = getViewBinding()
return binding.root
}
Then change this line
override fun getViewBinding(view: View): DemoFragmentBinding = DemoFragmentBinding.bind(view)
with
override fun getViewBinding() = DemoFragmentBinding.inflate(layoutInflater)
BaseFragment:
abstract class BaseFragment<VB : ViewBinding> : Fragment() {
private var _binding: VB? = null
val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = getViewBinding()
return binding.root
}
abstract fun getViewBinding(): VB
}
DemoFragment:
class DemoFragment : BaseFragment<DemoFragmentBinding>() {
override fun getViewBinding() = DemoFragmentBinding.inflate(layoutInflater)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply {
txtData.text = "Something"
}
}
}
Here is another way to implement this factory abstraction with ViewBinding. I am sharing the implementation code below. I am using genrics here. If anyone needs further explanation, I am here for that. Make sure you have enabled viewbinding feature already into the build.gradle file. Then use the following BaseFragment.kt as your fragment abstraction.
BaseFragment:
typealias Inflate<T> = (LayoutInflater, ViewGroup?, Boolean) -> T
abstract class BaseFragment<V: ViewBinding>(
private val inflate: Inflate<V>
) : Fragment() {
private lateinit var _binding: V
val binding get() = _binding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = inflate(inflater, container, false)
return binding.root
}
}
N:B: Know more about typealias.
HomeFragment:
// Implement the BaseFragment like below
class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::inflate) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// usages by calling public variable 'binding' from base class
binding.message.text = "update $value"
}
}
I looked through the documentation, I'm not sure if this breaks what you wanted with the abstract pattern in the BaseFragment, but I tested your code with this change and it worked. Only change was in the DemoFragment:
//Add this for the onCreateView implementation
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = DemoFragmentBinding.inflate(LayoutInflater.from(context), null, false)
val view = binding!!.root
return view
}
Then I tested in onViewCreated:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//problem is here
binding?.txtData?.text="Something"
}
And it worked. The documentation I looked at was this section
I think doing this still allows you to avoid some boilerplate, but the onCreateView override is going to be necessary for every fragment, because of the different viewbinding inflation (e.g. DemoFragmentBinding.inflate etc etc)
Uses https://github.com/hoc081098/ViewBindingDelegate
abstract class BaseFragment<VB : ViewBinding>(#LayoutRes layoutId: Int) : Fragment(layoutId) {
protected abstract val binding: VB
}
import com.hoc081098.viewbindingdelegate.*
class MainFragment: BaseFragment<FragmentMainBinding>(R.layout.fragment_main) {
override val binding by viewBinding()
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
lateinit var db:Database
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db=Room.databaseBuilder(applicationContext,Database::class.java,"Users").build() //Error is shown here
}
}
AddUserFragment.kt
class AddUserFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view= inflater.inflate(R.layout.fragment_add_user, container, false)
view.btn_add.setOnClickListener {
val id=et_Uid.text.toString()
val name=et_Name.text.toString()
val email=et_Email.text.toString()
val users=Users(id,name,email)
val mainActivity=MainActivity()
mainActivity.db.myDao().addUser(users) //Error is shown here
Toast.makeText(activity,"Added",Toast.LENGTH_LONG).show()
}
return view
}}
Where to initialize db so not to get kotlin.UninitializedPropertyAccessException: lateinit property db has not been initialized
You are creating fresh instance of MainActivity which is not required!
Every Fragment has the activity instance which they associated with
Just cast that activity as MainActivity
(activity!! as MainActivity).db.myDao().addUser(users)