RadListView not responding to new items in an ObservableArray - android

I'm trying to render a list of images, using RadListView. Being that i had some bizarre behavior when the data was coming from a normal array, i decided to try ObservableArray, as recommended in the docs.(specifically tns-vue)
The problem is, that pushing a new item to the model, doesn't update the view. The item is there, but nothing is shown.
This is my RadListView:
<RadListView layout="grid" ref="newImages" for="image in newImages">
<v-template>
<ImageComponent
showDate="false"
:onLongPress="()=>{onLongPress(image)}"
:image="image"
></ImageComponent>
</v-template>
</RadListView>
The "newImages" array:
data() {
return {
newImages: new ObservableArray([]),
};
}
I add items to the array, using the camera plugin:
openGallery() {
var that = this;
console.log(that.newImages.length);
var context = imagepicker.create({ mode: "multiple" }); // use "multiple" for multiple selection
context
.authorize()
.then(function() {
return context.present();
})
.then(function(selection) {
const images = [];
selection.forEach(function(selected) {
const image = that.createNewFileSchema(selected._android);
images.push(image);
});
that.newImages.push(images)//This adds the images to the array, but UI doesn't respond to the change.
})
.catch(function(e) {
alert(e);
});
},
What could be the problem here?

Your pushing arrays to a data arrays, to make the virtual DOM notice these changes, you probably wan't to use a deep watcher, calling a method returning the updated array.
You would have the same problem with Objects, but then you would be able to use:
this.$set(<object>, <key>, <value>)
I'm unsure if there is a better way for arrays, but you could try a watcher as said
watch: {
newImages: {
handler: function(<value>, <oldValue>) {},
deep: true
}
}
UPDATED - You can use this.$set for arrays
https://v2.vuejs.org/v2/api/#Vue-set
/* this.$set(<Array>, <Index>, <Value>) */
this.$set(this.newImages, this.newImages.length, [...newArrWithImages])
This guy explains reactively updating arrays: Vuejs and Vue.set(), update array

Related

Removing elements from a list that is used by liveData does not remove any

Android 4.1.2
Kotlin 1.4.21
I have the following live data that I add to, but when it comes to removing it doesn't remove any elements.
val selectedLiveData by lazy { MutableLiveData<List<Core>>() }
I don't want to trigger the observers so I am not assigning the value as I just want to remove a single element from the liveData list and only trigger when adding.
None of the following work
selectedLiveData.value?.toMutableList()?.apply {
removeAt(0)
}
selectedLiveData.value?.toMutableList()?.apply {
removeFirst()
}
selectedLiveData.value?.toMutableList()?.apply {
remove(Core)
}
I am adding my elements like this and then assigning the value so the observers to this live data get updated:
selectedLiveData.value = selectedLiveData.value?.toMutableList()?.apply {
add(core)
}
What you wanted is
val selectedLiveData = MutableLiveData<List<Core>>(emptyList())
Then
selectedLiveData.value = selectedLiveData.value.toMutableList().apply {
removeAt(0)
}.toList()
So what are you doing exactly:
You create a MutableLiveData with a List of objects. As we know in Kotlin List is immutable, so it's readonly.
If you want to add / remove items from a list, you should use MutableList.
If we look the documentation of toMutableList which you are using:
/**
* Returns a new [MutableList] filled with all elements of this collection.
*/
public fun <T> Collection<T>.toMutableList(): MutableList<T> {
return ArrayList(this)
}
So every time you try to remove an item via:
selectedLiveData.value?.toMutableList()
you actually perform that operation on a new MutableList not the original one.
If you want to add / remove I suggest you to use MutableList in your MutableLiveData so you can create something similar to this:
private val selectedLiveData = MutableLiveData<MutableList<Int>>()
// Init
selectedLiveData.value = mutableListOf(100, 200)
// Add items
selectedLiveData.value?.add(2)
selectedLiveData.value?.add(10)
selectedLiveData.value?.add(50)
// Remove item
selectedLiveData.value?.remove(2)
selectedLiveData.postValue(selectedLiveData.value.toMutableList().apply {
removeAt(0)
}.toList())

Adding/removing items from Listview programatically?

I'm trying to create an app for my Samsung Gear S3 that takes data from an API, dynamically creates a Listview, and propagates it using the data returned via the API. I've got as far as putting the items in the list, but when the page is hidden I want to remove everything from the list. Since I'm doing it programmatically in the first place, there's no easy way such as removing all of the elements using a querySelector and refreshing the Listview.
That said, much how I'm using .addItem - is there a .removeItem or equivalent that isn't documented?
At the moment, I'm listening to the pagebeforehide event and calling .destroy() on the created Listview - which works in the sense that the _items property is cleared but the Listview is still displaying all of the items from before...
The code I have at the moment can be seen below.
(function() {
var page = document.getElementById('search.results'),
listView;
var elems = [ ];
page.addEventListener("pagebeforeshow", function() {
if(sessionStorage.length && sessionStorage.getItem("currentResults")) {
var currentResults = JSON.parse(sessionStorage.getItem("currentResults"));
var elem = document.getElementById("results-list");
listView = tau.widget.Listview(elem, { dataLength: currentResults.length, bufferSize: 10 });
listView.setListItemUpdater(function(listElement, newIndex) {
var data = currentResults[newIndex];
listElement.innerHTML = data.Description;
listElement.id = data.EAN;
});
}
$("li").click(function(e) {
var li = $(e.target);
if(li.attr("id")) {
var EAN = li.attr("id");
tau.changePage("product.html", { name: li.innerHTML, ean: EAN });
}
});
});
page.addEventListener("pagebeforehide", function() {
console.log("page before hide");
if(sessionStorage.length && sessionStorage.getItem("currentResults")) {
console.log(sessionStorage);
sessionStorage.removeItem("currentResults");
console.log(sessionStorage);
}
listView.destroy();
console.log(listView);
});
}());
This list does not actually have an API for removing items or binding to data.
I see you have solved this problem practically by yourself.
I think it is enough to clear the base element of the list now, eg. listView.element.innerHTML = "" or document.getElementById("results-list").innerHTML = "".
I think you can also create issue on https://github.com/Samsung/TAU/issues
because the .destroy() method should remove items which was not attached to widget before widget build.

How to order data on the fly

I'm a beginner with react native or firebase so I don't really know how to explain this but I have no idea on how to order received data from the database.
Let's say I have a database:
appname
items
-some_random_generated_string
-name: "someString"
-value: "999"
-type: 0
-some_random_generated_string
-name: "someString"
-value: "999"
-type: 0
I've tried already with all order types and also setting .indexOn rule but haven't come to a solution. Also tried adding an id and then order with it and didn't come to a solution.
I guess this is accessing the database to get items so I also tried ordering them on the same line but nothing worked except limiting the amount of data.
let itemsRef = db.ref('items');
then I have:
componentDidMount() {
itemsRef.on('value', snapshot => {
let data = snapshot.val();
let items = Object.values(data);
this.setState({ items });
});
}
and I'm adding like this:
let addItem= (item, value, type) => {
db.ref('/items').push({
name: item,
value: value,
type: type,
});
};
Basically what I want to achieve is to display data in reversed order than it was added so the last one added would be shown on the top.
You could do it in two ways.
First simply call .reverse() on your current array. If you call the push method to add new items, usually the key that's assigned to each child garanties they are stored in chronological order. Therefore, calling it as such should be good enough:
componentDidMount() {
itemsRef.on('value', snapshot => {
let data = snapshot.val();
let items = Object.values(data);
items.rerverse();
this.setState({ items });
});
}
Though i don't know if let items = Object.values(data); garanties on every browser that your data are ordered as intended. By the way Object.values() opposed to Object.keys() is not supported on many old browsers, but if you're using it in ReactNative then it's fine.
If you want to be fully sure it's properly ordered, rewrite it as such:
componentDidMount() {
itemsRef.on('value', snapshot => {
let items = [];
// forEach always send you the children in the right order
snapshot.forEach(snap => {
items.push(snap.val())
})
items.rerverse();
this.setState({ items });
});
}
Finally another way to do it if you don't trust firebase's auto generated ids, is to add objects as such:
let addItem = (item, value, type) => {
db.ref('/items').push({
name: item,
value: value,
type: type,
timestamp: new Date().getTime()
});
};
and to use firebase's function orderByChild when you fetch the data, using timestamp as the child, and then .reverse() it again like we did previously.

Combine, Zip or how to include or squeeze in a stream into a stream

I am not sure what exactly to use but lately, I have had a lot of trouble with RxJava when I am working with code that has streams for everything.
In my case, let say I have to get an instance of an object, that I need for some processing from a stream that is available, let's call this NeededInstance and so I have access to Observable of NeededInstance.
Next, what I am doing is I have a Single of a List of SomeObject and what I do is I need to iterate over all items and update them.
I do this in the following way:
.map(/*in this map the Single<List<SomeObject>> is created*/)
.flatMap(Single<List<SomeObject>> -> updateWithData(Single<List<SomeObject>>);
this is how I wanted my updateWithData function to look like:
private Single<List<SomeObject>> updateWithData(List<SomeObject> list) {
return
Observable.just(list)
.flatMapIterable(listItem -> listItem)
.flatMapSingle(listItem -> updateListItem(listItem))
.toList();
}
I do the above code so that I can transform a chain from handling a single list to an observable of items that I update and return to a list again. Below is the updateListItem function, where trouble comes when I try to get something from that other stream I mention in the beginning:
updateListItem(ListItem listItem) {
return
Observable<NeededInstance>.map(Optional::get)
.flatMapSingle(neededInstance -> workWithListItemAndNeededInstace(listItem, neededInstance))
.map(integer -> {
// do something with integer soming from the above method and with a listItem passed into this function
}
return Single.just(updatedListItem)
}
so, to be sure, workWithListItemAndNeededInstance can't update the listItem, I just get an Integer object there and with that, I have to do my own updating of listItem. Then I am trying to either return a Single of a listItem or listItem itself and somehow make it available for a .toList() so that in the end I still have a Single of a List of ListItem in the stream.
I am trying with combine but can't really make it work and I find RxJava a bit weird when I have streams that I need to just "drop in" and leave something that I can use for processing.
Any clarification is welcome.
//You have a list of string object
List<String> intList = new ArrayList<>();
Collections.addAll(intList, "1", "2", "3", "4", "5");
//Now what you want here is append neededInstance to each item in list and get it as a list.
//So output would be like List of (test1, test2, test3, test4, test5);
Observable
//iterate through the list of items and pass one by one to below stream
.fromIterable(intList)
//Each item from the list is passed down to workWithListItemAndNeededInstace
.flatMap(item -> workWithListItemAndNeededInstace(item))
.toList()
.subscribe();
/*
This method will combine item from list with the neededInstance and return a stream of combined data
*/
private Observable<String> workWithListItemAndNeededInstace(String item) {
return neededInstance().map(instance -> instance + item);
}
/*
This will be your stream from which you get needed stream
*/
private Observable<String> neededInstance() {
return Observable.just("Need instance");
}
Hope this solution gives you a rough idea on what you would want to achieve. Let me know if I missed anything, so that I can update this answer.

MvxAutoCompleteTextView dropdown re-opens on every keypress

I have a problem whereby upon every keypress from the user, the AutoCompleteTextView quickly hides and re-appears again (with an updated set of values).
Please suggest where my problem could be and whether you see any other problems with the below code.
Binding in the View:
bindingSet
.Bind(emailAutoCompleteTextView)
.For(t => t.Text)
.To(vm => vm.Email);
bindingSet
.Bind(emailAutoCompleteTextView)
.For(t => t.PartialText)
.To(vm => vm.CurrentEmailEntry);
bindingSet
.Bind(emailAutoCompleteTextView)
.For(t => t.ItemsSource)
.To(vm => vm.CurrentEmailAutoCompleteSuggestions);
AXML Layout:
<mvvmcross.droid.support.v7.appcompat.widget.MvxAppCompatAutoCompleteTextView
android:id="#+id/EmailAutoCompleteTextView"
android:layout_marginTop="#dimen/PaddingBetweenUserInputFields"
android:completionThreshold="1"
android:inputType="textEmailAddress" />
View Model Code:
private string _currentEmailEntry;
public string CurrentEmailEntry
{
get
{
return _currentEmailEntry;
}
set
{
_currentEmailEntry = value;
if (value == string.Empty)
{
_currentEmailEntry = null;
}
CurrentEmailAutoCompleteSuggestions = _emailAutoCompleteList
.Where(email => email.StartsWith(_currentEmailEntry, StringComparison.OrdinalIgnoreCase))
.ToArray();
RaisePropertyChanged(nameof(CurrentEmailEntry));
}
}
private static readonly string[] _emailAutoCompleteList = {"Gordon", "Gordy", "Go", "Freeman", "Is", "Alive"};
private IList<string> _currentEmailAutoCompleteSuggestions = _emailAutoCompleteList.ToList();
public IList<string> CurrentEmailAutoCompleteSuggestions
{
get { return _currentEmailAutoCompleteSuggestions; }
set
{
if (ReferenceEquals(_currentEmailAutoCompleteSuggestions, value))
return;
_currentEmailAutoCompleteSuggestions = value;
RaisePropertyChanged(nameof(CurrentEmailAutoCompleteSuggestions));
}
}
I use MvvmCross 4.0 (upgrade is not an option).
NB: I tried using an ObservableCollection instead of an IList and remove/add items to it (not re-assign the collection itself) but after that the setter for CurrentEmailEntry stopped receiving values after the user typed the first character into the text view. The code inside MvxFilteringAdapter seems to be stuck waiting on a reset event.
Since no one answered either here or on the MvvmCross Xamarin Slack channel I eventually discovered the solution myself.
It was the re-creation of the list bound to the ItemsSource that led to the strange behaviour in question.
The ObservableCollection with Clear()/Add() instead of re-creating was indeed the way to go. The stuck behaviour (waiting on the reset event) described in the last question paragraph was caused by the absence of proper thread dispatching (my application is multi-threaded).
As soon as I wrapped my observable collection with a proxy collection that always raised the CollectionChanged event on the UI thread, the problem disappeared.
Leaving this here for the benefit of future generations.

Categories

Resources