ViewModelProviders.of(activity) error - android-studio

I have no idea what to do anymore but ask here.
When i try to access the ViewModel from a fragment attached to an activity:
private val userViewModel by lazy { ViewModelProviders.of(activity).get(UserProfileViewModel::class.java) }
i get an error for "activity" saying "Type mismatch: inferred type is FragmentActivity? but FragmentActivity was expected"
every example i've seen so far is using it this way and i just can't get it to work.
Not sure if this is deprecated and i should just give up on it.

Your problem is, that activity can be null when the viewmodel is lazy loaded, which means the type of activity is FragmentActivity? instead of the required FragmentActivity.
The ViewModel Initialization is usually not done with a delegate, but in a lifecycle method, where you are sure you are attached to an activity, like in onViewCreated() or onActivityCreated().
There you can safely use:
userViewModel = ViewModelProviders.of(activity!!).get(UserProfileViewModel::class.java)

Related

Kotlin: How to pass data from SharedViewModel to Adapter

I'm working on an app with a bottom navigation bar and struggle when trying to access a sharedViewModel's data across fragments and adapters. My code is already quite full so I'll be trying to list it up in short here. If needed, I can of course supply the whole code.
Because of different fragments having to access the same data, I created a sharedViewModel class DeactivatedElementsViewModel (that's where things start to get complicated). In an exemplary fragment there is the fragment class DeactivatedJumpElementsFragment : Fragment(R.layout.deactivated_jump_elements_fragment) accompanied by the adapter myAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() which equips a list inside the fragment with buttons and strings.
In the fragment class I begin with lateinit var sharedViewModel: DeactivatedElementsViewModel and lateinit var elementeAusStand: Map<String,Array<Any>>before successfully working with the sharedViewModel in e.g. onViewCreated(inside the fragment) like so elementsFromStand= sharedViewModel.elementsFromStand.
Now I struggle with accessing data from the sharedViewModel inside the adapter's function onBindViewHolder.
I tried different approaches like the following:
directly loading elementsFromStand inside onBindViewHolderby implementing the sharedViewModel there which leads to the error "Can't access ViewModels from detached fragment"
loading elementsFromStand inside onBindViewHolder via the sharedViewModel declared in the fragment's class like val elementsFromStand= DeactivatedJumpElementsFragment().elementsFromStand which led to the error of the sharedViewModel being called before initialized. I tried to intercept this by
if (DeactivatedJumpElementsFragment()::elementsFromStand.isInitialized){val elementsFromStand = DeactivatedJumpElementsFragment().elementsFromStand} which simply
won't ever be true/ run in runtime although the variable
elementsFromStandis indeed initialized in onViewCreated()
using nested functions, trying to call the variable elementsFromStand via a function getSharedViewModelVariable from onCreate() but I fail to successfully retrieve it this way.
That's where I need help. How do I (easily?) access the view model's variables from my adapter?
Thanks for reading and for any hint!
The problem you are facing is that you are trying to access the sharedViewModel from the adapter, which is not a lifecycle owner and does not have a reference to the fragment's view. A possible solution is to pass the sharedViewModel as a parameter to the adapter's constructor, and then use it in the onBindViewHolder method. For example:
// In your fragment class, initialize the sharedViewModel and the adapter
private val sharedViewModel: DeactivatedElementsViewModel by activityViewModels()
private lateinit var adapter: MyAdapter
// In your onViewCreated method, create the adapter instance and pass the sharedViewModel
adapter = MyAdapter(sharedViewModel)
recyclerView.adapter = adapter
// In your adapter class, accept the sharedViewModel as a parameter and store it as a property
class MyAdapter(private val sharedViewModel: DeactivatedElementsViewModel) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
// In your onBindViewHolder method, use the sharedViewModel to access the data
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val elementsFromStand = sharedViewModel.elementsFromStand
// Do something with the elementsFromStand
}
}
This way, you can access the sharedViewModel's data from the adapter without creating a new instance of the fragment or using detached views. However, you should also be careful about updating the data in the sharedViewModel, as it might affect other fragments that are using it. You might want to use LiveData or other observable patterns to handle data changes and notify the adapter accordingly.
Explanation:
The reason why you can't access the sharedViewModel from the adapter directly is that the adapter is not a lifecycle owner, which means it does not have a lifecycle that is tied to the fragment's view. The sharedViewModel is scoped to the activity's lifecycle, which means it can be shared by multiple fragments, but it also requires a lifecycle owner to access it. The fragment is a lifecycle owner, and it can access the sharedViewModel by using the activityViewModels() delegate, which provides the same instance of the sharedViewModel to all the fragments in the same activity. However, the adapter is not a lifecycle owner, and it does not have a reference to the fragment's view, so it can't use the activityViewModels() delegate or the viewModels() delegate (which provides a fragment-specific instance of the viewModel).
One way to solve this problem is to pass the sharedViewModel as a parameter to the adapter's constructor, and then store it as a property in the adapter class. This way, the adapter can access the sharedViewModel's data from the onBindViewHolder method, which is called when the adapter binds the data to the view holder. This approach is simple and straightforward, but it also has some drawbacks. For example, if the data in the sharedViewModel changes, the adapter might not be aware of it, and it might display outdated or inconsistent data. To avoid this, you might want to use LiveData or other observable patterns to observe the data changes in the sharedViewModel and notify the adapter to update the view accordingly. You might also want to consider the impact of updating the data in the sharedViewModel from the adapter, as it might affect other fragments that are using the same sharedViewModel. You might want to use some logic or events to coordinate the data updates and avoid conflicts or errors.
Can you try;
Writing a function in your adapter such as updateData() and call it in your fragment or activity and set the data you get from sharedViewModel. in example, i have a favourites list in recycler view and i get the data from my view model, i added this func. in my adapter class;
fun updateList(myList : List<WhateverYourClassOrType>) {
favouritesList.clear()
favouritesList.addAll(myList)
notifyDataSetChanged()
}
and in my fragment, i get the data from viewModel and set new data in it with;
myAdapter.updateList(newList)
Hope this is what you need.

Groovy - Correct way to implement getProperty method

I need to run some code whenever a property value is retrieved, so naturally it made sense to define the getProperty method in my class. This method will get automatically called whenever a property value is retrieved. Here's roughly what I have in my class:
class MyClass
{
def getProperty(String name)
{
// Run some code ...
return this.#"${name}"
}
}
The problem with the above method occurs when someone tries to make the following call somewhere:
MyClass.class
This call ends up in the getProperty method looking for a property named "class", however, there is not actual property named "class" so we get a MissingFieldException.
What would be the correct way to implement running code whenever a property value is retrieved and deal with these kind of situtations.
Best is not to have a getProperty method if not needed. If you need one and you want to fall back on standard Groovy logic, then you can use return getMetaClass().getProperty(this, property), as can be found in GroovyObjectSupport. This will cover more than just fields.
This seems to be a common problem with this method. Map has the same issue. The developers of groovy got around the problem with Map by saying you need to use getClass() directly.

Initializing custom view in Android Studio: Expected resource of type xml

I have a custom view called IconView, which has the following constructor for initialization:
public class IconView extends RelativeLayout implements Checkable {
...
public IconView(Context context, AttributeSet attrs, boolean useDefaultImage) {
super(context, attrs);
inflateLayout(context);
...
In order to initialize AttributeSet instance from XMLfor constructing the view, I use getResources().getXml(R.layout.icon_view), false);.
This compiles successfully and runs correctly. However, Android studio highlights the code and displays me this error:
The detailed description of the error is here:
Expected resource of type xml less... (Ctrl+F1)
Reports two types of problems:
Supplying the wrong type of resource identifier. For example, when
calling Resources.getString(int id), you should be passing
R.string.something, not R.drawable.something.
Passing the wrong
constant to a method which expects one of a specific set of
constants. For example, when calling View#setLayoutDirection, the
parameter must be android.view.View.LAYOUT_DIRECTION_LTR or
android.view.View.LAYOUT_DIRECTION_RTL.
The question:
Although the code works, I do not know, how to rewrite it, so that the error would disappear in Android Studio. The error is visually annoying, so how could I get rid of it?
Resources#getXml(int id) is used to get the resource of type xml, which lays inside the xml resource folder. You, on the other hand, passing R.layout.icon_view here, the layout resource.
Use getResources().getLayout(R.layout.icon_view) here and the error will disappear.
P.S.: the documentation on Resources#getLayout() says the following:
This function is really a simple wrapper for calling getXml(int) with a layout resource.
So it looks like this is some kind of lint-related issue. Either way, getLayout() does not result in this error.

Why ContractAnnotationAttribute of ReSharper cannot target constructors?

What is the reason why this static analysis attribute is not allowed to be declared on constructors? I would like to annotate that a constructor throws an exception when a null argument is passed.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
internal sealed class ContractAnnotationAttribute : Attribute
{
//...
}
PS: I am asking in a hope that there is a some sort of general principle for this and not just their personal decision.
There's an open ticket to add support here which you can vote on, and track: http://youtrack.jetbrains.com/issue/RSRP-401969
However, the only annotation you can apply is that a parameter must be not-null. You can achieve pretty much the same result using the NotNull annotation on those parameters.

Checking for an attribute on a destination property inside a custom AutoMapper TypeConverter

I have a custom type converter that converts UTC DateTime properties to a company's local time (talked about here: Globally apply value resolver with AutoMapper).
I'd now like to only have this converter do its thing if the property on the view model is tagged with a custom DisplayInLocalTime attribute.
Inside the type converter, if I implement the raw ITypeConvert<TSource, TDestination> interface, I can check if the destination view model property being converted has the attribute:
public class LocalizedDateTimeConverter : ITypeConverter<DateTime, DateTime>
{
public DateTime Convert(ResolutionContext context)
{
var shouldConvert = context.Parent.DestinationType
.GetProperty(context.MemberName)
.GetCustomAttributes(false)[0].GetType() == typeof(DisplayInLocalTimeAttribute);
if (shouldConvert) {
// rest of the conversion logic...
}
}
}
So this code works just fine (obviously there's more error checking and variables in there for readability).
My questions:
Is this the correct way to go about this? I haven't found anything Googling around or spelunking through the AutoMapper code base.
How would I unit test this? I can set the parent destination type on the ResolutionContext being passed in with a bit of funkiness, but can't set the member name as all implementors of IMemberAccessor are internal to AutoMapper. This, and the fact that it's super ugly to setup, makes me this isn't really supported or I'm going about it all wrong.
I'm using the latest TeamCity build of AutoMapper, BTW.
Don't unit test this, use an integration test. Just write a mapping test that actually calls AutoMapper, verifying that whatever use case this type converter is there to support works from the outside.
As a general rule, unit tests on extension points of someone else's API don't have as much value to me. Instead, I try to go through the front door and make sure that I've configured the extension point correctly as well.

Resources