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.
Related
What is the best way to do the following:
I have one json like:
{
"restaurants":[
{
"id":1,
"name":"Chinese Food",
"neighborhood":"New york",
"photograph":"abc.jpg",
"cuisine_type":"Chinese"
}, ..]
and second json response is
{ "menu":[
{
"restaurantId":1,
"allcategories":[
{
"id":"100",
"name":"Noodles",
"menu_items":[
{
"id":"800",
"name":"Hakka noodles",
"description":"Tasty hakka noodles",
"price":"350.00",
},
{
"id":"900",
"name":"Shezwan Hakka noodles",
"description":"Shezwan sauce spicy hakka noodles",
"price":"750.00",
}
]
}, ...],...}]}
I want to show all restaurants from first json response in recyclerview and along with it I want to show their respective menus also in the same card using the second response, where restaurant id is common in both the responses.
I have thought of couple of ways of doing it, like adding categoies and menu items in the first response/data class itself and then populating the UI from adapter or maybe using the common key in both the response as hashmap key and storing entire category from second response for each restaurant as value in hashmap and then in onbindViewholder finding category and then menu-items from second response object.
But this all seems little unclean, and i was thinking if there was a simpler way to achieve this using some kotlin operators. Can anyone suggest anything simpler and better?
P.S
I want to implement search filter also later, so I am looking for a solution in which i can search also between both the responses, like restaurant name from first response and menuitems from second response, and then my recyclerview will show the searched items.
Thanks in advance.
what I have done write now (using common key as Hashmap key) looks like this:
override fun onBindViewHolder(holder: RestaurantAdapter.RestViewHolder, position: Int) {
getCategoryList(restList.get(position).id)?.forEach {
it?.let {
holder.binding.tvMenuItem.text= "${it?.menuItems}"
}
}
}
fun getCategoryList(restId:Int?):ArrayList<Category?>?{
return menuMap.get(restId)?.categories
}
You can use
.filter { <filter predicate> } that returns a new list of items based on the condition you provided
map { } that that returns a list of items, where each item is transformed or modified from what you provided in the map function
You can find more Kotlin collection operations from the following links
https://github.com/benct/kotlin-cheat-sheet
https://medium.com/mobile-app-development-publication/kotlin-collection-functions-cheat-sheet-975371a96c4b
You can also perform a series of chained operations
My goal is to save a list with Secure Storage and add new items to this list. There will be a list. There will be items in it. I want to add an item to that list later. In the methods I tried, it did delete the entire list. I couldn't add an item to the already existing list.
How can I do it?
I am currently stuck here:
Future<void> listUpload() async {
final prefences = await SharedPreferences.getInstance();
prefences.setStringList("values", ["Value 1"]);
var printList = prefences.getStringList("testler");
debugPrint(printList.toString());
setState() {
}
}
Give this a go:
Future<void> listUpload() async {
final prefences = await SharedPreferences.getInstance();
// The debug print is unnecessary but there if you want it.
// var printList = prefences.getStringList("values");
// debugPrint(printList.toString());
setState(() {
prefences.setStringList("values", prefences.getStringList("values")! + ["Your value"]);
});
}
Couple things about your set state:
Firstly, it needed to be wrapped in one more set of brackets. ((){}) instead of (){}.
Also, as we can't exactly edit the list, let's just grab the list and whatever
value we need to the end of it.
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
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.
I am new to Xamarin Android Development and I am using MvvmCross for binding data.I have SerachView on action-bar.I want to search data from list which is in ViewModel.How can I implement that ? I have searched for this issue on internet but all have used adapter and i want to search list-item without using adapter from ViewModel.I am not getting any idea how to do that.So anyone can suggest me an easy way?
Any suggestion or advice will be appreciated.
It is pretty simple.
Your SearchView is bound to a string property which you are using for filtering. Here I assume it is called SearchQuery.
It is not clear what criteria you want to use for filtering, I will assume that the ViewModel has a Name property, where the SearchQuery will be contained in that name.
So your ViewModel would look something like:
public class SearchViewModel : MvxViewModel
{
public string SearchQuery
{
get { return _searchQuery; }
set {
_searchQuery = value;
RaisePropertyChanged(() => SearchQuery);
RaisePropertyChanged(() => FilteredResults);
}
}
public List<ListItemViewModel> UnfilteredResults
{
get { return _unfilteredResults; }
set {
_unfilteredResults = value;
RaisePropertyChanged(() => UnfilteredResults);
RaisePropertyChanged(() => FilteredResults);
}
}
public List<ListItemViewModel> FilteredResults
{
get
{
if (string.IsNullOrEmpty(SearchQuery))
return UnfilteredResults;
return UnfilteredResults
.Where(r => r.Name.Contains(SearchQuery)).ToList();
}
}
}
So what happens is, whenever you enter a new value into the search box, it will trigger the PropertyChanged event on FilteredResults and use a simple LINQ query to filter the results.
If you don't want to swap out the entire list every time, you can do this with an ObservableCollection and add and remove items in that instead.
EDIT:
So as stated above you just bind the MvxListView to the new items source. Assuming you are using a AXML layout for your view:
<MvxListView
..
local:MvxBind="ItemsSource FilteredResults; ItemClick ResultClickedCommand" />
As for the SearchView, I just looked, there does not seem to be any code in MvvmCross to easily bind to that, and it does not inherit from EditText, so you need to do something like described here: https://stackoverflow.com/a/22501906/368379
Easiest way is probably simply to implement the SearchView.IOnQueryTextListener interface and in there set the new string you receive in the implementation on your ViewModel.