I'm creating a Component/View that has a list of things to select from similar to a table view.
When I'm in View A I can jump to this table view by doing a navigator push. Once an item is selected I do a pop. However, I'd like a text component in View A to be updated with the appropriate value.
I was thinking of passing a reference to this text component but it doesn't sound right. Any other ways I could achieve that?
I couldn't find any table view that would work on both platforms, let me know if you have any good suggestions.
The React way to achieve this is to pass a callback function to a named attribute, let's say in this case onSelect.
MyParentComponent extends Component {
...
MySelectHandler(value) {
this.setState({
valueSelected: value
});
},
...
}
Creating your picker component:
<MyCustomTableView onSelect={this.MySelectHandler}/>
And in your component:
MyCustomTableView extends Component {
...
onValueSelected(value) {
this.props.onSelect(value);
}
...
}
Considering your "re-render" problem, React only updates Component who have changed depending on the previous state. As your inputs probably don't depend on the state, they don't get updated because nothing changed for them.
If you want to clear all your inputs when the valueSelected key state gets updated, you can use the lifecycle method componentDidUpdate(), and manually clear your inputs after each value update.
// In MyParentComponent
componentDidUpdate(prevProps, prevState) {
if (prevState.valueSelected !== this.state.valueSelected)
this.refs.myInput.value = ''; // Do this for each input, you'll need to add a unique ref attribute for each one
}
...
I solved it by passing {this} to the table view as a prop and calling this.props.parent.setState({selection: selectedThing}) in the table view.
I thought setState would re-render the whole view and get rid of any user inputs but it seems to only re-render changed components
Related
I am doing a side project of making an app (with Java since I already know it). I have a recyclerview which loads some data via the room database library. The elements of the recyclerview are clickable.
My problem is I want the user to be able to sort the recyclerview so that the most recently accessed items go to the top.
My original idea was to assign the entities to have two variables - a String list_name which also serves as the id, and an Int order_of_access. Also, in my ViewModel I have a getAllLists method which returns a livedata list. I have an onChanged listener in the fragment activity which nicely updates the recyclerview when data is added/removed.
When the user adds a new list, it is assigned an order_of_access of the listsize (+1). But when the user deletes a group of lists, or clicks on a list, I want to update the order_of_access, say with an updateOrderAccess method.
Do you think this is the best way of doing what I want?
Where should I place updateOrderAccess and how would you recommend it be written? Since the method getAllLists returns livedata, it is tempting to put updateOrderAccess in an observer in the fragment (in onChanged) - but this will obviously create an infinite loop. It seems more in the correct philosophy to put it in the ViewModel, but then how would you suggest the updateOrderAccess method to be written? I'm having some trouble conceptualising what I need.
I hope the question is not too vague - I will update it if you need more details.
Where should I place updateOrderAccess and how would you recommend it
be written?
I am so sure that you must write it in the view model, as long as updateOrderAccess() is editing the list which is observable then you have andexpose by that the ui state then you have to put it in view model, and the observers will be notified ( in this case it is recycle view) and it will redraw the list in the order you offered.
note: do not you ever update the state(ui data) outside the state holder so you implement UDF (unidirectional Data Flow) pattern.
see the references below to read more about UDF so you never get confused where to declare your functions by letting the architicture lead you:
Guide to app architecture
ui layer
state holders and ui state
Do you think this is the best way of doing what I want?
i am not very sure that i got exactly what your app do, but it seems like you want to re-order the elements of recycle view depending on the ui event (click) or data change (deleting or adding new element), now you have two choices:
if the order is very importnat to you that much you want to keep it even if the app has been destroyed
then you have to add a field in the room entity represent the ordering (let us call it order) and whenever the user click on the recycle view you have to update the rooms field "order" which is "flow" or "liveData" or any observable type, that will tell the view model that there is a changing in the data, now the view model have to re-order the new data by the field "order" and pass it to the recycle view to show it.
if your app do not have to save the order changes after the app been destroyed
then you can simply do that:
create list which is called "orderedList" you will put the list items in it by the right order, and another list called "unorderlist" which have getAllLists
for the first case where the ordering is being changed by user click, you
can declare a function in viewModel then use it in the ui
controller (your activity or fragment), so whenever the list item is
clicked this function just re-order the orderedList elements ( which
is observable, so the changes reflect on the ui ) just by change the
clicked item position to the front of the list.
for the second case where the ordering changes by data changes like
add or delet a list item in the database, then you have to compare
the legnth of orderlist and unorderlist legnth, if unorderList is
longer then it is an add situation else it is a delete situation, in
adding case just add the last item of unorderList to the orderList,
else you have to check the deleted item and delete it from
orderList.
I've been messing around with Jetpack Compose and currently looking at different ways of creating/managing/updating State.
The full code I'm referencing is on my github
I have made a list a piece of state 3 different ways and noticed differences in behavior. When the first list button is pressed, it causes all 3 buttons to be recomposed. When either of the other 2 lists are clicked though they log that the list has changed size, update their UI but trigger no recompose of the buttons ?
To clarify my question, why is that when I press the button for the firsList I get the following log messages, along with size updates:
Drawing first DO list button
Drawing List button
Drawing second DO list button
Drawing List button
Drawing third DO list button
Drawing List button
But when I press the buttons for the other 2 lists I only get the size update log messages ?
Size of list is now: 2
Size of list is now: 2
var firstList by remember{mutableStateOf(listOf("a"))}
val secondList: SnapshotStateList<String> = remember{ mutableStateListOf("a") }
val thirdList: MutableList<String> = remember{mutableStateListOf("a")}
Row(...) {
println("Drawing first DO list button")
ListButton(list = firstList){
firstList = firstList.plus("b")
}
println("Drawing second DO list button")
ListButton(list = secondList){
secondList.add("b")
}
println("Drawing third DO list button")
ListButton(list = thirdList){
thirdList.add("b")
}
}
When I click the button, it adds to the list and displays a value. I log what is being re-composed to help see what is happening.
#Composable
fun ListButton(modifier: Modifier = Modifier,list: List<String>, add: () -> Unit) {
println("Drawing List button")
Button(...,
onClick = {
add()
println("Size of list is now: ${list.size}")
}) {
Column(...) {
Text(text = "List button !")
Text(text = AllAboutStateUtil.alphabet[list.size-1])
}
}
}
I'd appreciate if someone could point me at the right area to look so I can understand this. Thank you for taking the time.
I'm no expert (Well,), but this clearly related to the mutability of the lists in concern. You see, Kotlin treats mutable and immutable lists differently (the reason why ListOf<T> offers no add/delete methods), which means they fundamentally differ in their functionality.
In your first case, your are using the immutable listOf(), which once created, cannot be modified. So, the plus must technically be creating a new list under the hood.
Now, since you are declaring the immutable list in the scope of the parent Composable, when you call plus on it, a new list is created, triggering recompositions in the entire Composable. This is because, as mentioned earlier, you are reading the variable inside the parent Composable's scope, which makes Compose figure that the entire Composable needs to reflect changes in that list object. Hence, the recompositions.
On the other hand, the type of list you use in the other two approaches is a SnapshotStateList<T>, specifically designed for list operations in Compose. Now, when you call its add, or other methods that alter its contents, a new object is not created, but a recomposition signal is sent out (this is not literal, just a way for you to understand). The way internals of recomposition work, SnapshotStateList<T> is designed to only trigger recompositions when an actual content-altering operation takes place, AND when some Composable is reading it's content. Hence, the only place where it triggered a recomposition was the list button that was reading the list size, for logging purposes.
In short, first approach triggers complete recompositions since it uses an immutable list which is re-created upon modification and hence the entire Composable is notified that something it is reading has changed. On the other hand, the other two approaches use the "correct" type of lists, making them behave as expected, i.e., only the direct readers of their CONTENT are notified, and that too, when the content (elements of the list) actually changes.
Clear?
EDIT:
EXPLANATION/CORRECTION OF BELOW PROPOSED THEORIES:
You didn't mention MutableListDos in your code, but I'm guessing it is the direct parent of the code you provided. So, no, your theory is not entirely correct, as in the immutable list is not being read in the lambda (only), but the moment and the exact scope where you are declaring it, you send the message that this value is being read then and there. Hence, even if you removed the lambda (and modified it from somewhere else somehow), it will still trigger the recompositions. The Row still does have a Composable scope, i.e., it is well able to undergo independent recompositions, but the variable itself is being declared (and hence read) in the parent Composable, outside the scope of the Row, it causes a recomp on the entire parent, not just the Row Composable.
I hope we're clear now.
Suppose I have two screens: HomeScreen & LocationScreen
Steps: Firstly I navigated from HomeScreen(state={location: 'A'}) to LocationScreen.
Changed location in LocationScreen(state={location: 'B'})
Pass it to HomeScreen and change location from A to B.
Now , HomeScreen has View dependent on location state.
So my question is , how can I update the view content of home screen as the content is coming from network response after the location been updated?
What you are suffering from is a common problem of state management in React. For easing your trouble, there is another library called Redux. Give it a read.
To answer your question, Redux provides a connect function which has two arguments : 1) mapStateToProps & 2) mapDispatchToProps. You can easily solve your problem with these two functions and a lifecycle method called "componentWillReceiveProps(nextProps)".
Here is an example you can refer to : https://stackoverflow.com/a/38203735/2164029
Redux is a great tool for app state management, especially for the state that is shared in a few places in the app.
In your use case though, I think the regular React state should be sufficient.
After you pass the changed location from Location screen back to Home screen, Home screen can in term trigger the fetching for data. By storing the content on the component state, you can easily refer to them in render function. this.setState triggers re-render of the component, as any state change or prop change will cause a re-render
e.g.
class HomeScreen extends Component {
...
onLocationChange(newLocation) {
this.setState({ loading: true, newLocation }); // Loading data, and storing new location
fetchDataBasedOnLocation().then((data) => {
this.setState({ content: data. loading: false });
});
}
render() {
return (
...
{Your content in this.state.content}
...
);
}
}
Hope this is helpful!
I hope you will be fine.
I am having trouble updating views in Titanium Appcelerator Alloy,
I basically want to be able delete previous children from picker and then add new one in picker that is in a different controller/view that i am currently in.....
I have followed this THIS SOLUTION unfortunately that is not working for me. Here is the code I am trying.
createEvent.js
Ti.App.addEventListener('db_update', function(){
alert("OK");
$.picker.removeAllChildren();
})
customParty.js
$.btnclick.addEventListener('click', function(){
Ti.App.fireEvent('db_update');
});
// OK alert shows up but the children of picker aren't removed.
Since Ok Alert is shown, you are in the good way and the callback function is called successfully.
The problem here is that calling removeAllChildren method is not removing rows from your picker. the solution is to iterate over colums and delete rows like this :
Ti.App.addEventListener('db_update', function(){
alert("OK");
//get picker columns
var columns=$.picker.getColumns();
//Iterate over picker columns
for (var it=0,length=columns.length;i<length;it++){
//iterate over column rows
if(columns[it]){
var len = col.rowCount;
for(var index=0,collength=columns[it].length;index<collength;index++){
//remove rows[index] of columns[it]
columns[it].removeRow(columns[it].rows[index]);
}
}
}
});
By the way Applcelerator's folks said that using global events (Ti.App events) may cause problems in memory managements...
Keep in mind that app-level events are global, which means they remain in context the entire time your app is running (unless you remove them). This also means that any objects they reference will also remain in scope while your app runs. This could prevent those objects from being garbage collected.
Appcelerator Documentation.
Another method is to use global functions:
In your first view controller (where picker is defined):
Alloy.Globals.removeAllPickerChildren=function(){
//do what you want here
};
then in the seconde viewcontroller:
$.btnclick.addEventListener('click', function(){
if(Alloy.Globals.removeAllPickerChildren)
Alloy.Globals.removeAllPickerChildren();
});
I'm trying to bind a MvxBindableListView in TwoWay mode, for it to update in the View when I Set it's value in the ViewModel (through a Buttons's Click command).
Currently it only updates when the layout is fully loaded at start/tabchange...
The ViewModel is:
public List<MyType> TestList
{
get { return _testList; }
set
{
_testList = value;
FirePropertyChanged("TestList");
}
}
The .axml in the View is:
<Mvx.MvxBindableListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="{'ItemsSource':{'Path':'TestList','Mode':'TwoWay'}}"
local:MvxItemTemplate="#layout/my_item_layout" />
The way data-binding works is through an interface called INotifyPropertyChanged
What happens in this interface is that the ViewModel sends the View a message whenever a property changes - e.g.
FirePropertyChanged("TestList");
With a list, this doesn't help if the contents of the list itself change - e.g. when the list has an item added or removed.
To solve this, the .Net Mvvm implementation includes another interface INotifyCollectionChanged.
A collection - such as a list - can implement INotifyCollectionChanged in order to let the View know when the contents of the collection change.
For example, the collection might fire events containing hints such as:
everything has changed - NotifyCollectionChangedAction.Reset
an item has been added - NotifyCollectionChangedAction.Add
an item has been removed - NotifyCollectionChangedAction.Remove
...
There's a short introduction into this interface about 12:30 into the MvvmCross Xaminar http://www.youtube.com/watch?v=jdiu_dH3z5k
To use this interface for a small in-memory list - e.g. less than 1000 'small' objects - all you have to do is to change your List<T> for an ObservableCollection<T> - the ObservableCollection is a class from the core .Net libraries (from Microsoft or Mono) and it will fire the correct events when you Add/Remove list items.
You can see the source for the Mono ObservableCollection implementation in: https://github.com/mosa/Mono-Class-Libraries/blob/master/mcs/class/System/System.Collections.ObjectModel/ObservableCollection.cs - it is worth taking some time to look at this implementation so that you can understand a bit more about how Mvvm works with INotifyCollectionChanged.
If you use the ObservableCollection class, then your code will become:
private ObservableCollection<MyType> _testList;
public ObservableCollection<MyType> TestList
{
get { return _testList; }
set
{
_testList = value;
FirePropertyChanged("TestList");
// in vNext use RaisePropertyChanged(() => TestList);
}
}
with:
<Mvx.MvxBindableListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
local:MvxBind="{'ItemsSource':{'Path':'TestList'}}"
local:MvxItemTemplate="#layout/my_item_layout" />
Note:
that the binding is OneWay - this means that binding is still only going from ViewModel to View - there are no updates going from View to ViewModel.
that ObservableCollection is designed to be single-threaded - so make sure all changes to the collection are done on the UI thread - not on a worker thread. If you need to, you can marshall work back onto the UI thread using InvokeOnMainThread(() => { /* do work here */ }) in a ViewModel.
that in Android, the way lists work (through the base AdapterView) means that every time you call any update on the ObservableCollection then the UI List will ignore the action hint (Add, Remove, etc) - it will treat every change as a Reset and this will cause the entire list to redraw.
For larger collections - where you don't want all the items in memory at the same time - you may need to implement some data-store backed list yourself.
There is a brief example of one simple sqlite data-backed store in https://github.com/slodge/MvvmCross/blob/vnext/Sample%20-%20SimpleDialogBinding/SimpleDroidSql.Core/DatabaseBackedObservableCollection.cs
This virtualizing of collection data is common in WP and WPF apps - e.g. see questions and answers like Is listbox virtualized by default in WP7 Mango?
We just found a workaround for this that works for us!!
NOTE: Adding and removing from the list updates the view with the new/removed item. However any changes to the state of the existing items were not reflected.
SOLUTION: We cleared our list and re-added the items to the ViewModel property with the updated state. Calling raisepropertychanged then mimics a two-way bind behavior. Essentially it was removing all values and re-adding all values.