I'm working with Titanium SDK 2.1.3 and developing for both iOS 6.1 and Android 4.0.
My application has a TableView, each TableViewRow represents a file and has a custom property called lockedStatus that can be either true or false, when a row is clicked, depending on the value of lockedStatus, a window is opened indicating the state of the element of the row. In this window I can modify the lockedStatus from true to false and viceversa.
The problem I'm having is that in Android the changes done to this property are ignored, unless a completely force the application to stop, this doesn't happen in iOS.
To change the lockedStatus value in the window, I fire an event like this:
Ti.App.fireEvent('updateLockedStatus', {
lockedStatus : true //this can be true or false
});
Then the listener is like this:
Ti.App.addEventListener('updateLockedStatus', function(e){
var rows = table.data[0].rows;
for (var i = 0; i < rows.length; i++) //iterate through the rows to look for the file
{
if (rows[i].fileID == currentlyEditingFile)
{
rows[i].updateLockedStatus(e.lockedStatus);
files[currentlyEditingFile].lockedStatus = e.lockedStatus; //update the file lockedStatus
rows[i].fileObject = files[currentlyEditingFile];
saveFilesToDatabase();
}
}
});
Each row is generated with a updateLockedStatus function, that has the following definition:
row.updateLockedStatus = function(lockedStatus)
{
row.lockedStatus = lockedStatus;
}
The saveFilesToDatabase function looks like this:
function saveFilesToDatabase()
{
var tempFilesArray = [];
for(var i=0;i<filesIds.length;i++)
{
tempFilesArray.push(files[filesIds[i]]);
}
Ti.App.Properties.setString('filesApp', JSON.stringify(tempFilesArray)); //save the file values in the application properties
}
What I mean by being ignored is that when I exit the window and return to the view with the table of files, if I click again on the file I just modified, the window will be opened as if no change had been done.
How can I force an update for all of the contents in the TableView? In iOS there's no problem, but with Android this isn't updated unless I close the application.
Any help will be appreciated.
try using Ti.API.fireEvent & Ti.API.addEventListener instead of Ti.App
you would take care when doing this :
Each row is generated with a updateLockedStatus function, that has the following definition:
row.updateLockedStatus = function(lockedStatus)
{
row.lockedStatus = lockedStatus;
};
If the code above is put in a loop then executing the row.updateLockedStatus may affect only the last row because the row variable gets reused and contains at the end only the last row object. Take a look at this
You can simply use this code :
row.updateLockedStatus = function(lockedStatus)
{
this.lockedStatus = lockedStatus; // this is the current row
};
Or using closures, something like that :
row.updateLockedStatus = (function(r){
return function(lockedStatus){
r.lockedStatus = lockedStatus; // r is the current row
};
})(row);
the local r variable should retain the right row object.
BTW, when you listen events using Ti.App, you should use variables for handlers. Instead of doing this :
Ti.App.addEventListener('updateLockedStatus', function(e){
use this way
var updateLockedStatusHandler = function(e){
//...
};
Ti.App.addEventListener('updateLockedStatus', updateLockedStatusHandler);
Then this could be useful to remove the handler and avoid a potential memory leak :
Ti.App.removeEventListener('updateLockedStatus', updateLockedStatusHandler);
Related
I am trying to validate an array in Android app written with Kotlin. It is an array of objects. This is the code that always returns 0. This might be even some deeper problem, but for now i am looking for any other way to get the count right.
private fun count(array: Array<Item>): Int {
val selectedItemCount = 0
array.forEach { item ->
if (item.isSelected) selectedItemCount + 1
}
return selectedItemCount
}
Basically my problem is that if the count is 0 and none items are selected i want to display no items selected message, otherwise navigate to next screen. I believe that i got this part right. When i log the count every time it returns 0 although the items selected are true within array.
Any help please?
You can improve the code by just using the count with predicate function to do the same in a cleaner way (in this example, the function counts all elements having isSelected set to true):
private fun count(array: Array<Item>): Int {
return array.count { it.isSelected }
}
There are a few problems in the original question:
in the first line, you create val (which is final type, so you can't change it's value). You can use var instead
this operation: selectedItemCount + 1 adds 1 to selectedItemCount and returns it's value (it's not modifying the input variable). You can use selectedItemCount += 1 operator instead (add and update the variable), or simply selectedItemCount++ if you just want to increment by one
Try this instead
array.forEach { item ->
if (item.isSelected) selectedItemCount += 1
}
Just to show a couple more tricks...
You could make an extension function like this:
// you can do count { it.isSelected } but sometimes it's nice to explicitly
// reference the property and the type like this
fun Array<Item>.countSelected() = count(Item::isSelected)
and call it with array.countSelected(). But it also feels a bit like a property of the array, right? How many are selected. So instead of calling a function, you could make it an extension property instead:
val Array<Item>.selectedCount get() = count(Item::isSelected)
And now you can just reference array.selectedCount like it's any other property on an object. Under the hood it does a calculation when you access it (it's a get() function really!) but it can just read nicer than a function call:
if (array.selectedCount == 0) displayNoItemsSelected()
I mean if you don't even care about the count, just whether something is selected, you could write an extension for that instead!
val Array<Item> noneSelected get() = count(Item::isSelected) == 0
// or (more efficient since it'll return early if one is selected)
val Array<Item> noneSelected get() = none(Item::isSelected)
// or if you have the other property too and want to reuse it
val Array<Item> noneSelected get() = selectedCount == 0
if (array.noneSelected) displayNoItemsSelected()
etc!
I'm working on a simple calorie counter app using two fragments and a ViewModel. I'm a beginner and this is a modification of an app I just created for a course (this app is not a homework assignment). It uses ViewModel and has a fragment that collects user input and a fragment that displays the input as a MutableList of MutableLiveData. I would like for the list screen to initially be empty except for a TextView with instructions, and I'd like the instructions to disappear once an entry has been added to the list. My class instructor told me to use an if-else statement in the fragment with the list to achieve this, but it's not working. He didn't tell me exactly where to put it. I tried a bunch of different spots but none of them worked. I don't get errors - just no change to the visibility of the TextView.
Here is the code for the ViewModel with the list:
val entryList: MutableLiveData<MutableList<Entry>>
get() = _entryList
init {
_entry = MutableLiveData<Entry>()
_entryList.value = mutableListOf()
}
fun addEntry(entryInfo: Entry){
_entry.value = entryInfo
_entryList.value?.add(_entry.value!!)
}
}
And this is the code for the observer in the list fragment:
Observer { entryList ->
val entryListView: View = inflater.inflate(R.layout.fragment_entry_list, null, false)
if (entryList.isNullOrEmpty()) {
entryListView.instructions_text_view.visibility = View.VISIBLE
} else {
entryListView.instructions_text_view.visibility = View.GONE
}
entryList.forEach {entry ->
val view: View = inflater.inflate(R.layout.entry_list_item, null, false)
view.date_entry_text_view.text = String.format(getString(R.string.date), entry.date)
view.calories_entry_text_view.text =
view.line_divider
binding.entryList.addView(view)
}
Thanks for any help.
I guess you are expecting your observer to get notified of the event when you are adding entryInfo to your event list (_entryList.value?.add(_entry.value!!).
But this won't happen as you are just adding an element to the same mutable list, and as the list reference hasn't changed, live data won't emit any update.
To solve this, you have two options.
Create a new boolean live data which controls when to show and hide the info text. Set its initial value to false, and update it to true in addEntry() function.
Instead of updating the same mutable list, create of copy of it, add the element and set the entryList.value equal to this new list. This way your observer will be notified of the new list.
Additionally, its generally not a good practice to expose mutable data unless there is no alternative. Here you are exposing a mutable list of Entry and that too in the form of a mutable live data. Ideally, your should be exposing LiveData<List<Entry>>.
This is one possible implementation of all the points that I mentioned:
private val _entryList = MutableLiveData(listOf<Entry>()) // Create a private mutable live data holding an empty entry list, to avoid the initial null value.
val entryList: LiveData<List<Entry>> = _entryList // Expose an immutable version of _entryList
fun addEntry(entryInfo: Entry) {
_entryList.value = entryList.value!! + entryInfo
}
I haven't used the _entry live data here, but you can implement it the same way.
set your viewModel to observe on entry added.
I think you have gotten your visibility toggle in the your if else blocks wrong.
if (entryList.isNullOrEmpty()) {
entryListView.instructions_text_view.visibility = View.GONE // OR View.INVISIBLE
} else {
entryListView.instructions_text_view.visibility = View.VISIBLE
}
Your Observer should get notified of changes to entryList when _entryList has changed. Make sure you are calling addEntry() function to trigger the notification.
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.
I have a list of CustomObject and I wish to remove a value from the list with same id(property of CustomObject).
Now this can be done by using loop. But that seems a very unnecessary and inefficient code to me. Is there any other way to get specific object with id?
If you don't want to use the removeIf method, you can do something like this:
yourList.apply { removeAt( first { it.id == id } ) }
If your project's min sdk version is 24 and up then you can use removeIf
yourList.removeIf(model -> model.id == yourid);
If you check the code inside removeIf, actually it also iterate
throughout the list to remove matching object.
Beside this, if you are using Kotlin or want to, Then you can filter the list with your id and then remove those from your list.
val yourList = arrayListOf<Model>()
val removeList = yourList.filter { model ->
model.id == yourid
}
yourList.removeAll(removeList)
I am currently trying to use the new approach of using FlexGlobals to access an ArrayCollection that I have on the Default view of a mobile application I am developing. Below is the code that makes up the creation and population of that array on the Default view which happens as soon as the app is initiated:
private var ids:ArrayCollection = new ArrayCollection();
private function loop():void
{
var index:int;
for( index = 0; index < compsCollection.length; index++ )
{
trace( "Element " + index + " is " + compsCollection[index].comp_id );
trace( ids.length);
ids.addItem(compsCollection[index].comp_id);
}
}
Now when this code is run i can clearly see from the console that the "ids" ArrayCollection is being populated correctly. Now on a different view within the app I want to access this data and use it for various things. I have used the code below to try and access the data for the ArrayCollection:
protected var ids_list:ArrayCollection = new ArrayCollection();
protected function view1_viewActivateHandler(event:ViewNavigatorEvent):void
{
var obj:Object = FlexGlobals.topLevelApplication.parameters;
ids_list.source = obj.ids;
trace(ids_list.length);
}
When i tried this i do not get an error however the Trace statement returns "0". So I also tried:
protected function view1_viewActivateHandler(event:ViewNavigatorEvent):void
{
ids_list.source = FlexGlobals.topLevelApplication.parameters.ids;
trace(ids_list.length);
}
Which again returned "0" in the trace statement. I finally tried this to see if it would work:
protected function view1_viewActivateHandler(event:ViewNavigatorEvent):void
{
ids_list.source = FlexGlobals.topLevelApplication.ids;
trace(ids_list.length);
}
When I tried this and navigated to the view where this code would be initated I got this error:
Error #1069: Property ids not found on Main and there is no default value.
I could only assume that the ArrayCollection I had created in my default view has no value once I navigate away from it. Is there anyway anyone can please help me with this as there seems to be next to no documentation on how to do these types of things with FlexGlobals? Thanks
You can't access a private property, try it
/*
Replace
private var ids:ArrayCollection = new ArrayCollection();
by
*/
private var _ids:ArrayCollection = new ArrayCollection();
public set ids(value:ArrayCollection):void{
_ids = value;
}
public get ids():ArrayCollection{
return _ids
}
It's extremely bad practice for a child component to know anything at all about the structure of its parent/ancestor. That being said, the reason you can't see the value of the Application's member variable is because you've made it private. If you want it to be visible outside its own scope, you need to make it public.
Pass the ArrayCollection as a property to your view from the main application.