In an existing Android App project (MVVM is used as pattern) I have found a PagerAdapter to swipe several information within this adapter:
class InformationSlideAdapter(ctx: Context) : PagerAdapter() {
private var contextCopy = ctx
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val layoutInflater = contextCopy.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val layoutScreen = layoutInflater.inflate(R.layout.slide_item, null)
val imageView = layoutScreen.findViewById<ImageView>(R.id.iv_logo)
//..
}
}
Question 1: Why is there findViewById() used? Shouldn't databinding solve this issue also for PageAdapters?
Question 2: Whenever I find a context in any other class than my view (especially when MVVM was used), this is very anti pattern for me. Why was a context provided there? Is there a reason for not using
val imageView = container.findViewById<ImageView>(R.id.iv_logo)
without inflating the two lines ahead?
Question 3: Altough the code is working (for now).. How are copies handled in kotlin?
private var contextCopy = ctx
Here a complete new copy instance is created in Kotlin? E.g. when I flip the screen, the context in corresponding InformationSlideActivity handles this correctly, but my InformationSlideAdapter still has an old instance of context with the unflipped state?
Unfortunately I cant ask the coder, since he has gone.
Thnx in advance
Pav
Question 1:
databinding or synthetics could be used use instead of findViewById. This could be legacy code or given the id of the view, it is possible that there are more views with same id and the developer wanted to avoid ambiguity by searching in a specific layout.
Question 2:
Injecting or passing context (in most cases) is an antipattern. If the context is needed it can be retrieved from the container.
the difference between
layoutScreen.findViewById<ImageView>(R.id.iv_logo)
and
container.findViewById<ImageView>(R.id.iv_logo)
is that the first one is searching in the newly inflated layout, that may or may not be attached to the container at that point in time.
Question 3:
private var contextCopy = ctx
this makes no sense, it is just making a reference copy. Given that the context is passed as a contructor parameter it is possible that the developer didn't know that they could do the following:
class InformationSlideAdapter(val ctx: Context, list: List<RelevantItem>) : PagerAdapter() {
in either case the context will not be refreshed and to have a new instance the adapter should be recreated
As per the answer of question2, the context should not be passed to the adapter and instead retrieved from the container.
Related
I am trying to get a view model in two places, one in the MainActivity using:
val viewModel:MyViewModel by viewModels()
The Other place is inside a compose function using:
val viewModel:MyViewModel = hiltViewModel()
When I debug, it seems that those are two different objects. Is there anyway where I can get the same object in two places ?
Even though you solved your issue without needing the view model, the question remained unanswered so I am posting this in case someone else finds it helpful.
This answer explains how view model scopes are shared and how you can override it.
In case you are using Navigation component, this should help. However, if you don't want to pass down view models or override the provided ViewModelStoreOwners, you can access the parent activity's view model in any child composable like below.
val composeView = LocalView.current
val activityViewModel = composeView.findViewTreeViewModelStoreOwner()?.let {
hiltViewModel<MyViewModel>(it)
}
While exploring some repositories on GitHub I found some people define fields twice for example
private var _binding: FragmentBinding? = null
private val binding: FragmentBinding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentBinding.inflate(layoutInflater)
return binding.root
}
why do some developers do this and why not just define the field once
private var binding: FragmentBinding? = null
In this particular context this is done to avoid any memory leaks. Fragments outlive their view,which means any reference to the view must be set to null when view is destroyed. so to take care of the memory leak the reference to the binding object is set to null in onDestroyView. in your case onDestroyView should look as
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
so this takes care of the memory leak issue, but now there is another problem, every time you use the _binding to access some view, you need to use (!!) non-null assertion as _binding!!.myView.setOnClickListener etc.
In order to avoid this redundant use of (!!), there is another property binding, which is not null. so now you can simply write binding.myView.setOnClickListener.
The comments and other answer talking about you missing the concept are talking about a different concept than what you are asking about in your example code.
The concept they're talking about is using a more restrictive publicly exposed version of a property. Like making the public version of a MutableList exposed only as a read-only List. This is a good practice for encapsulation, but it is not what your example code is doing, although both involve using a backing property with an _ prefix.
Your example is with two private properties with the same type. This is done for Android view binding in a Fragment because it needs to be possible to set the property to null when the Fragment is detached, so the bound views are not leaked. But it is inconvenient to have to keep dealing with a nullable property in the Fragment code, since most of your uses of the binding will be in functions that are only called while the Fragment is attached and the binding reference is not null.
This code allows a backing nullable _binding property that stores the actual reference to the binding and can be set to null when the fragment is detached. The binding property has no backing field, so it cannot cause the views to be leaked, but you can use it as a non-null property, which is safe so long as you only call it while the Fragment is attached. It is similar to requireContext() and requireActivity(), which are conveniently non-null, but are only safe to use while the Fragment is attached.
I think you misunderstood the concept here!
It's paradigm in general programming that keep one private member field with the _ prefix while exposing another variable with the same name as public member of the class without _ prefix.
I.e. let's say you have an interface or an abstract class called "Foo" and it's inheritance/child class as "Bar".
interface Foo {}
class Bar : Foo
While using it's functionality into some other class (let's say 'Baz') you found out that creating object of "Bar" is good enough for you on this particular "Baz" class.
class Baz {
var bar: Bar? = null
}
But, at the same time you also want your caller/consumer of "Baz" class not to have access to this var bar: Bar? = null in such a way that it's immutable since it's internal variable used on "Baz".
This is the moment, where this paradigm comes into picture where you expose your bar as read only variable while reassigning/modifying it internally n number of times as well as of the type of it's parent to limit it's scope. something like below:
class Baz {
private var _bar: Bar? = null
val bar: Foo get() = _bar // Here we expose private variable _bar as Foo type only on getter as well
}
Side note: (This happens in Kotlin, because it provides default getters & setters and providing getter only on public one restricts it's usage to read-only outside the class/object)
Source - Kotlin Docs
These are called backing properties in kotlin.
Advantages of using a backing properties.
The variable with prefix _ will be private var.
var - So it is mutable.
private - So it can only be mutated in the same class, but it is not exposed to other files.
The other variable will be public val.
public - So it is accessible from any file.
val - It can only be read. Writing to val is restricted.
If the data has to changes from other classes, we can use a public method to do so. The advantages of using a method rather than the default constructor is we can add any custom validation logic we want before updating the private variable.
I migrated recently from BrowseFragment to BrowseSupportFragment in Kotlin for an Android TV app.
In the onActivityCreated I set some properties which rely on getting the color. To get the colors I use:
ContextCompat.getColor(context, R.color.fastlane_background);
The issue here is that context is nullable and getColor doesn't accept that.
Every time I need the context, do I need to do something like this:
val ctx = context ?: return
ContextCompat.getColor(ctx, R.color.fastlane_background);
Is this the recommended solution, are there better ways?
Use requireContext() to get a non-null Context associated with your Fragment.
I confused with MVVM concept that ViewModel should not reference View.
In my usecase , I have to use Databinding and wrapping the Drawable by LiveData and observe its value in xml view.
Base on suggestion from Android I implemented as below
https://developer.android.com/topic/libraries/architecture/viewmodel
If the ViewModel needs the Application context, for example to find a
system service, it can extend the AndroidViewModel class and have a
constructor that receives the Application in the constructor, since
Application class extends Context.
MyViewModel.kt
class MyViewModel(application: Application): AndroidViewModel(application){
private val _showIcon = MutableLiveData<Drawable>
val showIcon: LiveData<Drawable>
get() = _showIcon
fun applyChanged(){
if(condition){
_showIcon.value = AppCompatResources.getDrawable(getApplication(),R.drawable.icon1)
}else{
_showIcon.value = null
}
}
}
main_activity.xml
android:drawableTop="#{viewModel.showIcon}"
Question:
This approach is OK with MVVM concept ? Is there anything I have to do with context inside ViewModel to prevent leak memory problem?
Or any potential problem in my code ?
Thank you so much !
I don't see any need to use databinding or view models for what you want to do. Just refer the drawable directly in xml file. If it is null, it won't be there. This is valid because you are getting the image resource from your own resources. If you were supposed to get any drawable from server or local database your approach would make sense.
I have a piece of code using AndroidAnnotations which is very similar to the one found at:
https://github.com/excilys/androidannotations/wiki/Adapters-and-lists
However - I want to pass an argument to the List adapter to specify which list - i.e.
#AfterInject
void initAdapter() {
persons = personFinder.findAll(companyName);
}
What is the best way to associate companyName with the Adapter? I can't use the constructor with AnroidAnnotations - and #AfterViews is called before the #AfterViews of the parent fragment, so I can't call setters then.
I have currently hacked in a call to set the params manually then refresh the view and removed the #AfterViews - but its nasty and unreliable as I duplicate the pattern down the hierarchy.
EDIT
Just calling the setter works in the most simple case - and is what I currently have.
But doesn't work well in the more complicated case. i.e
EFragment->EViewGroup->EBean ListAdapter
Since I can't use the constructor, I have to wait until the full hierarchy is rendered and laid out before the fragment tells the ViewGroup which Company to show company info, which in turn tells the ListAdapter which company so I can get which people, etc.
It doesn't take much effort for it to get very messy and if my data was on the web - the UI would probably render like a webpage from the 90s.
I was hoping to use something like #Extras - or have a way to pass arguments for #AfterInject to use, or even just put the companyId in the Fragment Context without tying my ListAdapter to only work with one type of Fragment...
Try this
#EFragment
public class MyFragment extends Fragment {
#FragmentArg("myStringArgument")
String myMessage;
#FragmentArg
String anotherStringArgument;
#FragmentArg("myDateExtra")
Date myDateArgumentWithDefaultValue = new Date();
}
Source:
https://github.com/excilys/androidannotations/wiki/FragmentArg