I am trying to download an image into an ImageView inside a Fragment, but it seems like the network request does not even get made by Glide. (I use Proxyman to watch network traffic from my physical Android device). I am not sure what else to try.
Here is the fragment code:
class ExampleFragment : Fragment(R.layout.fragment_example) {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var viewModel: ExampleViewModel
private lateinit var binding: FragmentExampleBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentExampleBinding.inflate(layoutInflater)
(activity as ExampleActivity).appComponent.inject(this)
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ExampleViewModel::class.java)
// I have also tried `activity as FragmentActivity` instead of this
Glide.with(this).load("https://www.popwebdesign.net/popart_blog/wp-content/uploads/2018/01/tiny-png-panda.jpg")
.override(200)
.into(binding.imageViewFaceTaggingStart)
}
}
Extra details:
I have read http://bumptech.github.io/glide/doc/debugging.html#missing-images-and-local-logs which mentions a few things I could try. I can confirm I call into(), am not using Custom Targets, and have explicitly set the size and background colour of the image view to ensure it is not "zero width".
I am using FragmentContainerView inside the Activity.
Here is a simplification of the XML file:
<androidx.constraintlayout.widget.ConstraintLayout ... >
<androidx.appcompat.widget.Toolbar ...></androidx.appcompat.widget.Toolbar>
<ScrollView ...>
<LinearLayout ...>
<ImageView
android:id="#+id/image_view_face_tagging_start"
android:layout_width="200dp"
android:layout_height="200dp"
android:visibility="visible"
android:background="#android:color/black"
android:scaleType="centerCrop"/>
....more views (TextView, Space, Checkbox)
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
The view that is inflated and the view reference i.e. binding that you are using are not same,
class ExampleFragment : Fragment() {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var viewModel: ExampleViewModel
private lateinit var binding: FragmentExampleBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentExampleBinding.inflate(layoutInflater)
return this.binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity as ExampleActivity).appComponent.inject(this)
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ExampleViewModel::class.java)
// I have also tried `activity as FragmentActivity` instead of this
Glide.with(this).load("https://www.popwebdesign.net/popart_blog/wp-content/uploads/2018/01/tiny-png-panda.jpg")
.override(200)
.into(binding.imageViewFaceTaggingStart)
}
}
Now the view of the fragment is the binding, Try with this.
issue in here class ExampleFragment : Fragment(R.layout.fragment_example)
Now the fragment will inflate this R.layout.fragment_example not the binding, so the binding refers to another view,
Related
I have tried this problem all days long and still cannot be resolved.
I am writing an app which has three fragments, connected by Navigation Component (with only one activity).
Fragment A
Fragment B
Fragment C
And I observed one LiveData variable from ViewModel in these three fragments.
But only Fragment B is responding. The other two are not.
I have read almost all similar problems from this stack over flow. But still can't see the answer.
Please suggest me.
I have tried removing the observer from FragmentB (which is ok with observing Live Data). But the other two is not still observing)
Here is my code:
Fragment B: (Observe Working)
class ServerFragment : Fragment() {
private lateinit var binding: ServerLayoutBinding
private val vm: DataModel by viewModels()
private lateinit var devices_observer: Observer<MutableList<ClientSocket>>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = ServerLayoutBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var client_list_adapter = ClientListAdapter(layoutInflater)
binding.clientListRcv.apply {
layoutManager = LinearLayoutManager(this.context)
addItemDecoration(
DividerItemDecoration(
this.context,
DividerItemDecoration.VERTICAL
)
)
adapter = client_list_adapter
}
client_list_adapter.submitList(vm.clientDevices.value?.toList())
vm.clientDevices.observe(viewLifecycleOwner, Observer {
client_list_adapter.submitList(vm.clientDevices.value?.toList())} )
}
}
Fragment C: (Observe Not Working)
class FileListFragment: Fragment() {
private val vm: DataModel by viewModels()
private lateinit var binding: FileListBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding=FileListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Here I am populating my recycler view with ViewModel data
//And ViewModel live dat is obeserving here and
vm.clientDevices.observe(viewLifecycleOwner, Observer{
///Here UI updating work will be done. But did'not responded. Here is the problem point I think.
Toast.makeText(context, "OKKK", Toast.LENGTH_SHORT).show()
})
}
}
I'm playing around with Kotlin on Android and one thing makes me confused.
When I converted few Fragments from Java to Kotlin I got this:
class XFragment : Fragment() {
private var binding: FragmentXBinding? = null
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentUhfReadBinding.inflate(inflater, container, false)
return binding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding!!.slPower.addOnChangeListener(this)
binding!!.btnClearTagList.setOnClickListener(this)
}
// ...
private fun updateUi(){
binding!!.someTextView.text = getSomeTextViewText()
binding!!.someSlider.value = getSomeSliderValue()
}
}
I can't make binding non-nullable, because it has to be initialized after XFragment class constructor, in onCreateView() or later.
So with this approach it has to be nullable and I have to put !! everywhere.
Is there some way to avoid these !!?
The official documentation suggests this strategy:
private var _binding: FragmentXBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
Ultimately, it becomes just like requireActivity() and requireContext(). You just need to remember not to use it in a callback that might get called outside the view lifecycle.
Note, you can create your view using the super-constructor layout parameter and then bind to the pre-existing view in onViewCreated. Then you might not even need to have it in a property. I rarely need to do anything with it outside onViewCreated() and functions directly called by it:
class XFragment : Fragment(R.layout.fragment_x) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentXBinding.bind(view)
binding.slPower.addOnChangeListener(this)
binding.btnClearTagList.setOnClickListener(this)
}
}
I'm trying to implement a BaseFragment in which I will pass the layout resource on it and it should outputs the binding to work in the fragment itself instead of need to do it everytime the fragment is extended.
For example I have this BaseFragment
open class BaseFragment(#LayoutRes contentLayoutId : Int = 0) : Fragment(contentLayoutId) {
private lateinit var onInteractionListener: OnFragmentInteractionListener
val toolbar : Toolbar?
get() {
return if(activity is BaseActivity)
(activity as BaseActivity).toolbar
else
null
}
override fun onAttach(context: Context) {
super.onAttach(context)
setOnInteractionListener(context)
}
...
In which I use like this
class A(): BaseFragment(R.layout.myFragment) { ... }
Now, if I use this I will need to do the definition of the binding class again in my onCreateView
class A(): BaseFragment(R.layout.myFragment) {
private lateinit var binding: MyFragmentBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.myFragment, container, false)
return binding.root
}
override fun onDestroy(){
binding = null
}
}
What I want to implement is that since I'm passwing the layout to my BaseFragment, I want my BaseFragment to handle the creation of the binding and just return me the binding in the fragment which I use to extend BaseFragment
What I want to have is something like this
class A(): BaseFragment(R.layout.myFragment) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.myTextView = ""
}
}
So my question is, how I can implement inside BaseFragment the onDestroy() and the onCreateView to always create a binding for me from the layout I'm passing in ?
I heard that I should use reflection but I'm not that sure on how to accomplish it
I didn't hear about the possibility to get the databinding just from a layout, but even if it's possible, I don't think that is the recommended way, because of two reasons:
Reflection is slow
It makes things more complicated than they are.
Instead of making magic with Reflection, you could do something like this:
abstract class BaseFragment<out VB: ViewDataBinding>(
private val layout: Int,
// Other Dependencies if wanted
) : Fragment() {
abstract val viewModel: ViewModel
// other variables that all fragments need
// This does not cause any memory leak, because you are not storing the binding property.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = DataBindingUtil.inflate<VB>(inflater, layout, container, false).apply {
lifecycleOwner = viewLifecycleOwner
setVariable(BR.viewModel, viewModel)
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Do some basic work here that all fragments need
// like a progressbar that all fragments share, or a button, toolbar etc.
}
And then, when you still need the bindingProperty, I would suggest the following library (it handles all the onDestoryView stuff etc):
implementation 'com.kirich1409.viewbindingpropertydelegate:viewbindingpropertydelegate:1.2.2'
You can then use this like:
class YourFragment(yourLayout: Int) : BaseFragment<YourBindingClass>() {
private val yourBinding: YourBindingClass by viewBinding()
override val viewModel: YourViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// do binding stuff
}
}
Let me know if this worked for you.
Cheers
I have a very weird problem. When I navigate from Fragment 1 to Fragment 2 using a btn.setOnClickListener and then navigating back from Fragment 2 to Fragment 1 using the back button, the btn.setOnClickListener in Fragment 1 does not work anymore and therefore is not able to navigate to Fragment 2 again.
Here is my code:
Button XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
// Custom Background for the button
<com.google.android.material.appbar.MaterialToolbar
android:clickable="false"
android:id="#+id/materialToolbar"
android:layout_width="match_parent"
android:layout_height="90dp"
android:background="#color/btnColorGray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
</com.google.android.material.appbar.MaterialToolbar>
<com.google.android.material.button.MaterialButton
android:clickable="true"
android:focusable="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
Main XML
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.view.fragments.home.calibrateAndRepair.CalibrateRepairMessageFragment">
... some other stuff
<!-- Included the button -->
<include
android:id="#+id/calibrate_repair_btn"
layout="#layout/calibrate_repair_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
BaseFragment for databinding
abstract class BaseFragment<out T: ViewDataBinding>(val layout: Int) : Fragment() {
abstract val viewModel: ViewModel
private val _navController by lazy { findNavController() }
val navController: NavController
get() = _navController
fun navigateTo(fragment: Int, bundle: Bundle? = null) {
_navController.navigate(fragment, bundle)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return DataBindingUtil.inflate<T>(inflater, layout, container, false).apply {
lifecycleOwner = viewLifecycleOwner
setVariable(BR.viewModel, viewModel)
Timber.d("Created BaseFragment and binded View")
}.root
}
}
EmailFragment for initializing the button
abstract class EmailFragment<out T: ViewDataBinding>(
layout: Int,
private val progressBarDescription: ArrayList<String>,
private val stateNumber: StateProgressBar.StateNumber
) : BaseFragment<T>(layout) {
abstract val next: Int
abstract val bundleNext: Bundle?
// getting the button from the button.xml
private val btnNext: MaterialButton by lazy { btn_next_calibrate }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// ... some other initializing which constantly work!
initButton()
}
// Initializing the button
private fun initButton() {
btnNext.setOnClickListener {
navigateTo(next, bundleNext)
Timber.d("Button clicked")
}
}
}
Fragment 1
#AndroidEntryPoint
class CalibrateRepairMessageFragment(
private val progressBarDescription: ArrayList<String>,
#StateNumberOne private val stateNumber: StateProgressBar.StateNumber,
) : EmailFragment<FragmentCalibrateRepairMessageBinding>(
R.layout.fragment_calibrate_repair_message,
progressBarDescription,
stateNumber
) {
// Overriding the values from EmailFragment
override val next: Int by lazy { R.id.action_calibrateRepairMessageFragment_to_calibrateRepairUserDataFragment }
override val bundleNext: Bundle by lazy { bundleOf("calibrate_repair_toolbar_text" to toolbarText) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// ... some other stuff
}
}
Fragment 2
#AndroidEntryPoint
class CalibrateRepairUserDataFragment(
private val progressBarDescription: ArrayList<String>,
#StateNumberTwo private val stateNumber: StateProgressBar.StateNumber,
) : EmailFragment<FragmentCalibrateRepairUserDataBinding>(
R.layout.fragment_calibrate_repair_user_data,
progressBarDescription,
stateNumber
) {
override val next: Int by lazy { R.id.action_calibrateRepairUserDataFragment_to_calibrateRepairDataOverviewFragment }
override val bundleNext: Bundle by lazy { bundleOf("calibrate_repair_toolbar_text" to toolbarText) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
I tried to delete everything that is not important for the question. You can ignore the constructor of BaseFragment, EmailFragment, CalibrateRepairMessageFragment and CalibrateRepairUserDataFragment. I am using navigation component and dagger-hilt.
I appreciate every help, thank you.
P.S: I've noticed that using button:onClick in the .xml file solves this problem but in this situation I can't use the xml version.
The problem with this should be your lazy initialization of btnNext.
The Fragment1 state is saved when navigating to Fragment2. When navigating back the XML View will be reloaded but the lazy value of btnNext won't change as it is already initialized and is pointing to the old reference of the button view. Thus your OnClickListener will always be set to the old reference.
Instead of assigning your button lazily you should assign it in EmailFragment's onCreateView()
PS: Also from btn_next_calibrate I suppose you are using kotlin synthetic view binding. If so you would not have to use a class variable.
im trying to make a base bottom sheet dialog fragment class that supports data binding. here is my class:
abstract class RoundedBottomSheetDialogFragment<VM : BaseViewModel, DB : ViewDataBinding> :
BottomSheetDialogFragment() {
abstract val viewModel: VM
open lateinit var binding: DB
private fun init(inflater: LayoutInflater, container: ViewGroup?) {
binding = DataBindingUtil.inflate(inflater, getLayoutRes(), container, true)
}
abstract fun getLayoutRes(): Int
abstract fun configEvents()
abstract fun bindObservables()
/**
*
* You need override this method.
* And you need to set viewModel to binding: binding.viewModel = viewModel
*
*/
abstract fun initBinding()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val parentLayout = inflater.inflate(R.layout.rounded_bottom_sheet, container, false)
init(inflater, parentLayout.container)
showDialogAsExpanded()
return parentLayout
}
private fun showDialogAsExpanded() {
dialog?.setOnShowListener {
val bottomSheetInternal =
(dialog as BottomSheetDialog).findViewById<View>(R.id.design_bottom_sheet) ?: return#setOnShowListener
val behavior = BottomSheetBehavior.from(bottomSheetInternal)
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.skipCollapsed = true
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
configEvents()
bindObservables()
}
}
if you see i'm inflating a layout inside this dialog fragment class and i want to use data binding inside that layout xml file.
this is an example of my xml file:
<layout>
<data>
<variable
name="vm"
type="com.mobtakerteam.walleto.ui.login.searchcountry.SearchCountryViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/search_list"
android:layout_width="match_parent"
android:layout_height="400dp"
app:data="#{vm.countries}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="20"
tools:listitem="#layout/fragment_search_country_row" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
but the problem is that it is not working in the xml layout and i have to manually observe the live data objects inside kotlin class like this:
viewModel.countries.observe(this, Observer {
adapter.submitList(it)
})
so what is the problem?
Using LiveData with binding you have to set lifecycle owner like this
binding.lifecycleOwner = this