Updating the view of an item from a CollectionView - chaplinjs

I'm trying to update the view of an item belonging to a CollectionView.
The best way should call "render()" from that particular view?
Despite the model change
I think I'm missing something important here, because, although the model has changed, calling "render()", does not update the view.
Thanks for your help!
Regards!

The best way to call "render()" is to subscribe CollectionView to some event for example "update_collection_view". Read more on Chaplin.mediator. This is common chaplin mechanism to communicate and exchange information, this prevent memory leaks. Often event raises in "fetch()" method of collection or model after loading data from server.
define [
'chaplin'
'views/base/view'
'views/base/collection_view'
], (Chaplin, View, CollectionView) ->
class SomeView extends View
template: template1
template = null
foo: ->
#here we update collection view
Chaplin.mediator.publish 'update_collection_view'
class SomeCollectionView extends CollectionView
itemView: SomeView
template: template2
template = null
initialize: ->
super
#subscribeEvent 'update_collection_view', #render

Related

Epoxy : how to use the #AutoModel annotation and handle clicks without controller?

I am starting to use the epoxy library because I am looking for a library to write less code while using the RecyclerView component.
The EpoxyRecyclerView seems to be a great component since you can simply give the models (basically a simple list), thanks to the setModels method.
Question 1
The first question is : it is possible to automatically set an id to a model (using the #AutoModel ?) without using a controller ?
For example, I have the following DataBindingEpoxyModel class :
#EpoxyModelClass(layout = R.layout.item_header)
abstract class HeaderModel
: DataBindingEpoxyModel()
{
#StringRes
#EpoxyAttribute
var title: Int? = null
}
And I use it like this in my Fragment :
val models = mutableListOf<EpoxyModel<*>>()
models.add(HeaderModel_().title(R.string.catalogue_header_categories_title)
// [...]
recyclerView?.setModels(models)
This code crashes because I do not set an id to the HeaderModel_() instance. The AutoModel annotation have to be used on a field only, so is there a way to automatically set an id to my model instance with no controller ?
Question 2
The second question is : it is possible to handle a click without using a controller ?
Using the code of the question 1, how to handle a click on several widget of the layout (in my case, a click on the TextView or a click on the itemView) ? Is there a way to override the holder used by the DataBindingEpoxyModel in order to handle the click directly into the holder ?
In my case I do not want to define the OnClickListener as an attribute of my HeaderModel because I would like to define the same behavior for all models of the type HeaderModel (without using a controller).
Thank you in advance for you help !
Edit : I found the answer of the question 2. I just need to override one of the bind method of the DataBindingEpoxyModel. But I do not find a way to automatically set an id to a model without a controller and the #AutoModel annotation.
Here the answer from the github repo for the first question :
There is no way to automatically set an id (besides automodel, which I
wouldn't recommend these days anyway) - you should always set an id
manually on each model. It is best to do this by setting a human
readable string. This is detailed more in the wiki

How do I notify multiple Activities and Fragments of a dataset change?

Consider an Android app that displays lists of data and lets the user drill down into it. At some point the user makes a change to the data that needs to be reflected in more than one Activity on the back stack. What is the best pattern for achieving this?
First, let's check the cases.
There is a data source in your app. And its updates needs to reflect to multiple UI,
case 1: some UI maybe just need the whole data source
case 2: some UI needs to process the data first before showing on the UI
You need to somehow create a single source of truth for all the UI and reflect the changes to them when something happens.
I solved this problem in my project by using MVVM and ViewModel + LiveData from google's new architecture component. Why? Because they are life cycle aware! You can use RxJava to do the same thing.
1. model layer
It's a singleton, and expose the data source as a LiveData A. In the following code, it will be OrderLiveStore.liveData.
class OrderLiveStore(
private val orderStore: OrderStore
) {
var liveData: MutableLiveData<List<Order>> = MutableLiveData()
init {
liveData.value = orderStore.items
}
}
2. view model layer:
case 1: You just inject that model to the view model, and expose the LiveData A to the view. Underneath considering the fact of singleton, all views which connect to this view model will get the update, because the view model simply just returns the same property from a singleton variable. I manage the singleton by using dagger.
class OrdersViewModel
#Inject constructor(
orderLiveStore: OrderLiveStore
): ViewModel() {
// expose to the view directly
val orders: LiveData<List<Order>> = orderLiveStore.liveData
}
case 2: You still inject the model to the view model, but inside, you need to subscribe to it using Transformations.map, and do your processing, and expose the result to the view layer
class OrderViewModel(
orderLiveStore: OrderLiveStore,
private val orderId: String
) : ViewModel() {
// expose to the view after processing it
val order: LiveData<Order> = Transformations.map(orderLiveStore.liveData) {
getNeededOrderFromList(it)
}
private fun getNeededOrderFromList(orderList: List<Order>?): Order? {
// This method will be triggered every time orderStore.liveData gets updated
}
}
You can see that in case 1, I use dagger to inject because it fits the case. In case 2, I created the view model in view with custom parameter, because the view model needs some extra information to grab the needed pieces from the model layer. In my case, it is a orderId:String
3. view layer
Now it's simple, be it a fragment or an activity, you observe that data source and update your UI,
orderViewModel.orders.observe(this, Observer {
// update the ui
})
or more elegantly, you can bind the LiveData from view model directly to the xml with data binding if you don't need that much pre-processing.
4. What about the CRUD
Well, you just update the model layer DIRECTLY. But the action will be started from one of the 2 layers
view layer (if it's from a user)
view model layer (if it's a side-effect).
But even it's from the view, the view will still call methods on view model, and view model will call some methods on model layer or you can simply update it in the view model layer depends on the cases(because you get the single source of truth).
It's kind of like Redux pattern - a nearly unidirectional data flow, where every change will happen at model layer and reflect back to view model layer, then bubble up to view layer. It's easy to reason about your data flow.
5. And the result will be what you want.
Because everything is now connected to a single source of truth(a shared model layer), but, in a decoupled manner. Every layer does its own job.
6. One more tip
In order to get Transformations.map to work, you need to observe the result in the view, otherwise, that subscription from Transformations.map will not work at all.
You can put your shared data in a Service, then in your fragment/activity's onResume methods you can get updated data from there.
To update your current fragment/activity you can fire an event when you update data in the Service, registering your fragment/activity to catch it and consequently update your showed data. You could use OttoBus to achieve this goal

MVVM ViewModel Naming

I have the views named as actions such as "SelectMethod". Wondering, if it makes sense to name the associated view model as "SelectMethodViewModel" or should it not use the action (i.e. Select) in the naming? I am thinking classes should be things so does the action not make sense or am I over thinking this? I know this is a simple question, but it has a trickle effect in the application.
I would definitely go ahead and use the same name for your View and ViewModel. In fact I would take it one step further and add View to SelectMethod.
This basically ensures that we know the relationship and purpose of the two classes.
TestView
TestViewModel
SelectMethod is not a good name for a view. A method is not a class, but a view is a class and so a view is not a method. I would suggest:
SelectionView
SelectionViewModel

Selection of fragment View depending on selected item ViewModel type

Target MvvmCross, Android
Objective: A screen (ViewModel/View) where the user can select an animal group (Amphibians, Birds, Fish, Invertebrates, Mammals, Reptiles). When a group has been selected, a Fragment Views will will display information for that animal group. The fields and layout differ per animal group (e.g. fish don't have wings).
Although for this question I have chosen for animal group (which is pretty static), want the list animal groups to be flexible.
Simplified app structure:
MyApp.Core
ViewModels
MainViewModel
IAnimalGroupViewModel
AmphibiansViewModel
BirdsBViewModel
FishViewModel
MyApp.Droid
Layout
MainView
AmphibiansFragment
BirdsFragment
FishFragment
Views
MainView
AmphibiansFragment
BirdsFragment
FishFragment
The MainView.axml layout file will contain (a placeholder for) the fragment of the displayed animal group.
In WPF or WP8 app I could make use of a ContentPresenter and a Style to automatically display the selected ViewModel with its View.
How could I achieve something like that in Droid?
I could use a Switch/Case in the MainView.cs that sets the Fragment according to the type of the selected ViewGroup. But that means I have to modify the MainView every time I add a new View.
Any suggestions/ideas?
Currently MvvmCross doesn't provide any kind of automatic navigation mechanism for Fragments in the same way that it does for Activities.
However, within your use case, if you wanted to use a navigation approach, then you could automatically build a similar type of automated lookup/navigation mechanism.
To do this, the easiest developer root would probably be to use reflection to find a lookup dictionary for all the fragments
var fragments = from type in this.GetType().Assembly.GetTypes()
where typeof(IAnimalGroupView)..sAssignableFrom(type)
where type.Name.EndsWith("Fragment")
select type;
var lookup = fragments.ToDictionary(
x => x.Name.Substring(0, x.Name.Length - "Fragment".Length)
+ "ViewModel",
x => x);
With this in place, you could then create the fragments when they are needed - e.g.
assuming that you convert the Selection event via an ICommand on the ViewModel into a ShowViewModel<TViewModel> call
and assuming that you have a Custom Mvx presenter which intercepts these ShowViewModel requests and passes them to the activity (similar to the Fragment sample) - e.g.
public class CustomPresenter
: MvxAndroidViewPresenter
{
// how this gets set/cleared is up to you - possibly from `OnResume`/`OnPause` calls within your activities.
public IAnimalHostActivity AnimalHost { get; set; }
public override void Show(MvxViewModelRequest request)
{
if (AnimalHost != null && AnimalHost.Show(request))
return;
base.Show(request);
}
}
then your activity could implement Show using something like:
if (!lookup.ContainsKey(request.ViewModelType.Name))
return false;
var fragmentType = lookup[request.ViewModelType.Name];
var fragment = (IMvxFragmentView)Activator.Create(fragmentType);
fragment.LoadViewModelFrom(request);
var t = SupportFragmentManager.BeginTransaction();
t.Replace(Resource.Id.my_selected_fragment_holder, fragment);
t.Commit();
return true;
Notes:
if you aren't using ShowViewModel here then obviously this same approach could be adjusted... but this answer had to propose something...
in a larger multipage app, you would probably look to make this IAnimalHostActivity mechanism much more generic and use it in several places.

Pattern to manage views in backbone

Coming from GWT, Backbone seems to miss a built-in solution on how to handle the life-cycle of a view. In GWT, every activity, which is more or less the equivalent to a View in Backbone, is managed by an ActivityManager which calls onStart/onStop on the activity, passing the eventBus and the element where the Activity can be rendered in. On stop, the ActivityManager will unbind all events the activity has bind to the eventbus and remove the view from the DOM.
In Backbone, it's easy to bind the events to model and collection but you have to remove them manually and there is no common api method where you will do this.
So I'm looking for best practice pattern on how to manage views to ensure no killed or disabled views are listening unnecessary to events.
you are right,
there is no build in solution to that (yet).
however it is of course possible to extend backbone to provide this functionality,
Derick Bailey has written a blog post about this recently,
take a look here:
http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
this is by no means the holy grail, you are free to implement as you wish, but it is a very straight forward approach, for handling zombie views, now you still need to take care of other creatures crawling in your memory, but this is a start with the views at least!
I'm using a custom BaseView, which extends Backbone's remove method:
app.BaseView = Backbone.View.extend({
views: [], // array to keep a ref of child-views for proper disposal later on
remove: function() {
// dispose any sub-views
_.each(this.views || [], function(view) {
view.remove();
});
// if the inheriting class defines a custom on-remove method, call it!
_.isFunction(this.onRemove) && this.onRemove();
// unbind all events from this view
this.off();
// finally, call Backbone's default remove method to
// remove the view from the DOM
Backbone.View.prototype.remove.call(this);
}
}
There's still a catch: models and collections need to be disposed by hand, because you don't know if it's used by other views too.
Seems Marionette finally has the functionality I'm looking for.
I post my solution to manage view at https://github.com/thomasdao/Backbone-View-Manager.
The view manager will always cleanup existing view before create a new view. Now I will initialize a new view by:
newView = VM.createView("newView", function(){
return new View();
};
If I want to reuse a view instead of creating a new one, I can use
newView = VM.reuseView("newView", function() {
return new View();
}
The different between VM.reuseView and VM.createView is that, reuseView will look for existing view with name "newView", if found it will return to you. Else, it will execute the callback function and cache result. VM.createView will always execute the callback function and cleanup existing view for you. Hence you may like to use VM.createView if the views is dynamic and frequently change

Resources