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.
Related
I have a query listener where I would fetch data from the QueryListener object and send it to an API.
public class QueryListener implements QueryExecutionListener {
#Override
public void onSuccess(String funcName, QueryExecution qe, long durationNs) {
SparkSession sparkSession = qe.sparkSession();
//then fetch data from QueryExecution object
}
}
I want to have an identifier so that I can track which module/class the data belongs to. Say class X creates the spark session, and the listener gets called for it, I want a way to tell (inside the listener) that X was the class for which this listener was called and all the data inside the QueryExecution object belongs to that class.
I am not able to find this particular info from the spark session object. I tried checking methods like conf() but it doesn't have the info. Does anyone knows how to do it using the spark session object?
Also, I can't go with identifier like appName (sparkSession.sparkContext().appName()) because I don't have access to change the calling methods and I see that most of them don't have this parameter set. Hence, I would be getting some default value when using this. That's why I opted to go with class name for it.
Please let me know any solutions for this or any other alternatives which might work.
I process messages from a queue. I use data from the incoming message to determine which class to use to process the message; for example origin and type. I would use the combination of origin and type to look up a FQCN and use reflection to instantiate an object to process the message. At the moment these processing objects are all simple POJOs that implement a common interface. Hence I am using a strategy pattern.
The problem I am having is that all my external resources (mostly databases accessed via JPA) are injected (#Inject) and when I create the processing object as described above all these injected objects are null. The only way I know to populate these injected resources is to make each implementation of the interface a managed bean by adding #stateless. This alone does not solve the problem because the injected members are only populated if the class implementing the interface is itself injected (i.e. container managed) as opposed to being created by me.
Here is a made up example (sensitive details changed)
public interface MessageProcessor
{
public void processMessage(String xml);
}
#Stateless
public VisaCreateClient implements MessageProcessor
{
#Inject private DAL db;
…
}
public MasterCardCreateClient implements MessageProcessor…
In the database there is an entry "visa.createclient" = "fqcn.VisaCreateClient", so if the message origin is "Visa" and the type is "Create Client" I can look up the appropriate processing class. If I use reflection to create VisaCreateClient the db variable is always null. Even if I add the #Stateless and use reflection the db variable remains null. It's only when I inject VisaCreateClient will the db variable get populated. Like so:
#Stateless
public QueueReader
{
#Inject VisaCreateClient visaCreateClient;
#Inject MasterCardCreateClient masterCardCreateClient;
#Inject … many more times
private Map<String, MessageProcessor> processors...
private void init()
{
processors.put("visa.createclient", visaCreateClient);
processors.put("mastercard.createclient", masterCardCreateClient);
… many more times
}
}
Now I have dozens of message processors and if I have to inject each implementation then register it in the map I'll end up with dozens of injections. Also, should I add more processors I have to modify the QueueReader class to add the new injections and restart the server; with my old code I merely had to add an entry into the database and deploy the new processor on the class path - didn't even have to restart the server!
I have thought of two ways to resolve this:
Add an init(DAL db, OtherResource or, ...) method to the interface that gets called right after the message processor is created with reflection and pass the required resource. The resource itself was injected into the QueueReader.
Add an argument to the processMessage(String xml, Context context) where Context is just a map of resources that were injected into the QueueReader.
But does this approach mean that I will be using the same instance of the DAL object for every message processor? I believe it would and as long as there is no state involved I believe it is OK - any and all transactions will be started outside of the DAL class.
So my question is will my approach work? What are the risks of doing it that way? Is there a better way to use a strategy pattern to dynamically select an implementation where the implementation needs access to container managed resources?
Thanks for your time.
In a similar problem statement I used an extension to the processor interface to decide which type of data object it can handle. Then you can inject all variants of the handler via instance and simply use a loop:
public interface MessageProcessor
{
public boolean canHandle(String xml);
public void processMessage(String xml);
}
And in your queueReader:
#Inject
private Instance<MessageProcessor> allProcessors;
public void handleMessage(String xml) {
MessageProcessor processor = StreamSupport.stream(allProcessors.spliterator(), false)
.filter(proc -> proc.canHandle(xml))
.findFirst()
.orElseThrow(...);
processor.processMessage(xml);
}
This does not work on a running server, but to add a new processor simply implement and deploy.
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)
I'm using ObjectDB but also want to make the collection inside a persisted object observable, so I have declared it this way:
#OneToMany(fetch=FetchType.EAGER, cascade=CascadeType.ALL) List<Widget> widgets = [] as ObservableList
Later on, I create a listener closure and attach it:
def widgetChangeListener = {
log.debug "WIDGET CHANGE: $it"
}
widgets.addPropertyChangeListener(widgetChangeListener)
However, when I try to persist the collection, I get this error:
Attempt to store an instance of a non persistable type com.greymatter.strategy.Harness$_closure1 - field com.greymatter.strategy.Harness.widgetChangeListener (error 303)
Is there any way to make this collection persistable while keeping the closure volatile, so I can observe changes to it? ObjectDB has a #Transient annotation, but I'm not sure how to apply it to the closure. If I put it on the def of widgetChangeListener, I get a MissingMethodException.
Are ObjectDB and ObservableList mutually exclusive?
Groovy collections are not fully supported by ObjectDB (and by JPA in general). See the list of supported collections in JPA / ObjectDB on this ObjectDB Manual page.
If ObservableList works well, except the listeners, you may use JPA lifecycle events to clear listeners before persisting or updating the entity object (and then set them back if necessary).
Alternatively you can keep 2 list fields in your entity class. An ordinary List that will be persisted, and an ObservableList wrapper of that list that will be set as transient.
I'm having issues with my Application Object. I am currently using a Service to simulate incoming data from an electronic game board. This data is represented as a 2D boolean array. Every five seconds the Service uses a method of the Application Object to update the array (setDetectionMap()). This array is being read by a Thread in my main Activity using another method (getDetectionMap()). After some debugging I am almost positive that the main Activity is not seeing the changes. Here is the code for my Application Object:
public class ChessApplication extends Application{
private static ChessApplication singleton;
private boolean[][] detectionMap;
public static ChessApplication getInstance(){
return singleton;
}
#Override
public void onCreate() {
super.onCreate();
singleton=this;
detectionMap=new boolean[8][8];
}
public boolean[][] getDetectionMap(){
return detectionMap;
}
public void setDetectionMap(boolean[][] newMap){
detectionMap=newMap;
Log.d("Chess Application","Board Changed");
}
}
I've checked my Manifest, I've rewritten my object declaration a dozen times, I've added LogCat tags to make sure that the code is executing when I think it should be, and I've even implemented the supposedly redundant Singleton code. Any ideas what could be causing this? Incidentally can anyone tell me how to view variable states as the activity is running? Thanks in advance.
Is your Activity calling getDetectionMap() to get the new map after the update occurs?
Because otherwise, it's holding onto a reference to the old boolean[][] array, wheras setDetectionMap(...) isn't actually updating the current data structure, it's just updating the "detectionMap" variable to point to a different one. As such, your main activity won't be aware of the swapout until the next time it calls getDetectionMap.
Easy fix: in setDetectionMap, manually copy values from newMap into detectionMap. Or, update the Activity's reference so it's looking at the right map.
One other observation entirely unrelated to the original question: It's quite unusual to override Application during Android development, and is usually considered a "code smell" unless you have a really good reason for doing so. In this case I imagine it's so that you can communicate between your service and Activity, but you create a middle-man where one isn't entirely necessary. Here's a useful SO thread on how to communicate directly between the two :)