Improving user experience with toggling on/off over 300 CheckBoxes - java

I made a simple cooking app where user can select multiple items and get a list of recipes with selected ingredients. There are 2 modes of selecting the ingredients: by tags and by checkboxes. In the second mode you can enter the number of ingredients you wish and hit the button "randomize" or just press "Select/Deselect all ingredients" buttons. The problem started with the number of checkboxes over 300 (386, to be specific). Code is working fine, but the time of GUI remain frozen is too big.
So, I've got HashMap <String/CheckBox> checkBoxHashMap, where key is tag of the check box. To select all (same for deselect all, but with false value) I am using this code:
for (CheckBox chkBox: checkBoxHashMap.values()) {
chkBox.setChecked(true);
}
What I tried to use:
1) Using ProgressBar. The problem was, that animation of ProgressBar is frozen during the selection/deselection of all CheckBoxes. Plus, most of the times ProgressBar even didn't showed up (I hid the ProgressBar after all the CheckBoxes were selected;
2) Using chkBox.jumpDrawablesToCurrentState(); right after calling chkBox.setChecked(true); and it was the best I could acquire in performance, but still it lagged and didn't satisfied me.
What I want:
I want to make it more user friendly (frozen GUI is not user friendly at all, I believe). I want to implement soft toggling on/off all the CheckBoxes (chain-like animation) or something else like disabling animation and enabling it back after all the CheckBoxes have changed their state and redrawing them all together.
Is there a way to achieve something like that?

Honestly, if I were you, I'd use RecyclerView to handle this situation.
Displaying 300 items at the same time on the screen is never a good idea: user can't possibly need all of them at once, nor system should handle a load of this kind.
The concept is to recycle views, meaning that as soon as a view becomes invisible, resources required for it are disposed and can be reused for another data cell.
I made an introductory article about RecyclerView with lots of examples, take a look
In your particular case, just make appropriate data set for checkboxes and alter it asynchronously, using result of this operation on UI thread just to update RCV (it's a very lightweight operation compared to what you have right now)

The setChecked operation is expensive enough that performing it on a large number of objects in the foreground thread is leading to your UI freezing because the work takes too long and the app can't finish updating the UI within a refresh cycle. You should perform expensive operations in the background:
new AsyncTask() {
#Override
protected Object doInBackground(Object[] objects) {
for (CheckBox chkBox: checkBoxHashMap.values()) {
chkBox.setChecked(true);
}
return null;
}
}.execute();

Related

How can I receive DragEvents of an ongoing drag for newly added Views?

As far as I can tell, Views only receive DragEvents if they had implemented onDragEvent() or had set an OnDragListener before startDrag() (or startDragAndDrop() for API 24+) is called. They will then continue to receive additional drag events if they return true for DragEvent.ACTION_DRAG_STARTED.
However, I am looking for a way to receive DragEvents after a drag operation had already started, for Views that got added to the layout during the drag operation.
To illustrate why, my situation is roughly the following:
I have a ViewPager2 with ListView fragments whose list items can be dragged. If I drag an item over another item, I "enter" that item and a new ListView fragment is shown with new child items. This works fine.
However, since these new child items didn't exists at the time of starting the drag operation, they don't receive DragEvents when I continue to drag the item over those new items.
So, basically, I want to be able to enter multiple levels deep in one continuous drag operation.
Is it possible to have newly added Views receive DragEvents for an ongoing drag operation?
Okay, I've managed to solve it by re-dispatching the original DragEvent (the one with action DragEvent.ACTION_DRAG_STARTED) to my main component, which is an instance of ViewGroup.
On inspecting the source code for ViewGroup.dispatchDragEvent() I found that ViewGroup has a member variable called mChildrenInterestedInDrag that gets filled with children interested in drag events, when DragEvent.ACTION_DRAG_STARTED is received.
So when I called ViewGroup.dispatchDragEvent() with the original DragEvent, after I entered an item in the list to view its child items, those new ListView child items were now responding to additional DragEvents.
I'm a bit worried that this approach might yield unexpected side effects. If I come across such effects upon further testing, I'll update this answer. But for now it will do.
If somebody knows that this indeed might yield unexpected side effects and/or has a better solution I'd love to hear them.
Addendum:
Upon storing the original DragEvent for re-dispatching, I had to "clone" the event, because, while it worked properly in an API 19 emulator, on an API 19 phone the original DragEvent's action were continuously altered during dragging, so its action wouldn't reflect DragEvent.ACTION_DRAG_STARTED anymore and re-dispatching didn't have the desired effect of registering newly added Views as interested in the ongoing drag operation.
Since DragEvents cannot be cloned or constructed, I had to "clone" it with the help of a Parcel:
void storeStartEvent(DragEvent startEvent) {
Parcel parcel = Parcel.obtain();
startEvent.writeToParcel(parcel, 0);
// important to "rewind" the Parcel first
parcel.setDataPosition(0);
this.startEvent = DragEvent.CREATOR.createFromParcel(parcel);
}
Other than that, I didn't experience any noticeable problems yet, so it may have just solved my issue nicely.

How to keep a users selection even after they close the app? Android

So at the moment the user will select a colour, shape and time. When the app first starts up there is a default for each. What I want to happen is to keep the users selection for all 3 of these even after they close the app while it runs in the background.
If you need me to post anything to help ask away. Thanks.
After writing out what the problem was, it's not that they're not being saved it's the fact that the selections ARE being saved but not being displayed properly. I.E by a border or the dropdown menu results back to it's default value instead of the users selection.
So the values are being saved(my bad) but the app overwrites these saves and doesn't use the border or beings the dropdown menu back to it's default value.
circularImageView = (CircularImageView)findViewById(R.id.activity_main_silver_color_button);
circularImageView.setBorderColor(getResources().getColor(R.color.unselected_border));
circularImageView = (CircularImageView)findViewById(v.getId());
circularImageView.setBorderColor(getResources().getColor(R.color.selected_border));
These work independent of each other but when the border is selected, if a another color was selected then it becomes an unselected border. But what happens if "selected_border" state is never saved.
public static void saveLockScreenDuration(Context context, int duration){
getStoredPreferences(context).edit().putInt(SELECTED_DURATION, duration).apply();
public static int getLockScreenDuration(Context context){
return getStoredPreferences(context)
.getInt(SELECTED_DURATION, DEFAULT_DURATION);
}
This is where all the prefereces are stored. DEFAULT_DURATION and SELECTED_DURATION are in a class called constants.
Upon selection you can store selected values in Shared Preferences. And upon launching it again you can check if those values are null or not. And display accordingly. Read how to use Shared Preferences here.
if you talking about for activity restore data.
then see here.
https://developer.android.com/guide/components/activities/activity-lifecycle.html#saras
Correct me if i am wrong. I think you can use PreferenceManager.getDefaultSharedPreferences(Context context)
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
which is shared across all your Activity and Service classes. If you set an accessibility service to get the event when app is in background and used defaultsharedprefrences to store i think it will work for your case.

Random RSS Data

I'm kind of new to android so please bear with me.
I need help with something, I have a rss feed that has energy tips in it. Instead of displaying all of them inside a listview I would like to display them one at a time on an activity that has a timer that automatically switches them inside a textview this activity would also have two buttons "prev" and "next" which would override the timer. Is it possible? Any pointers would be appreciated.
Thanks,
It sounds like the kind of view you might be looking for is a ViewSwitcher. This will allow you to switch and animate between different views in your app. While you can imagine more elaborate configurations (really, anything), this might be a good choice to you that's already baked into the framework. How to control the view is a slightly different question, however, there are multiple ViewSwitcher tutorials out there, and you can easily implement a Timer which switches between the views. What you do is fairly simple: tell the timer to fire after some interval. If the user presses some button before the timer fires, cancel the timer and (perhaps) reset it?

When to use new layouts and when to use new activities?

I'm making a game in Android and I'm trying to add a set of menu screens. Each screen takes up the whole display and has various transitions available to other screens. As a rough summary, the menu screens are:
Start screen
Difficult select screen
Game screen.
Pause screen.
Game over screen.
And there are several different ways you can transition between screen:
1 -> 2
2 -> 3
3 -> 4 (pause game)
4 -> 1 (exit game)
4 -> 3 (resume game)
3 -> 5 (game ends)
Obviously, I need some stored state when moving between screens, such as the difficulty level select when starting a game and what the player's score is when the game over screen is shown.
Can anyone give me some advice for the easiest way to implement the above screens and transitions in Android? All the create/destroy/pause/resume methods make me nervous about writing brittle code if I'm not careful.
I'm not fond of using an Activity for each screen. It seems too heavy weight, having to pass data around using intents seems like a real pain and each screen isn't a useful module by itself. As the "back" button doesn't always go back to the previous screen either, my menu layout doesn't seem to fit the activity model well.
At the moment, I'm representing each screen as an XML layout file and
I have one activity. I set the different buttons on each layout to call setContentView to update the screen the main activity is showing (e.g. the pause button changes the layout to the pause screen). The activity holds onto all the state needed (e.g. the current difficulty level and the game high score), which makes it easy to share data between screens. This seems roughly similar to the LunarLander sample, except I'm using multiple screens.
Does what I have at the moment sound OK or am I not doing things the typical Android way? Is there a class I can use (e.g. something like ViewFlipper) that could make my life easier?
By the way, my game screen is implemented as a SurfaceView that stores the game state. I need the state in this view to persist between calls to setContentView (e.g. to resume from paused). Is the right idea to create the game view when the activity starts, keep a reference to it and then use this reference with setContentView whenever I want the game screen to appear?
This question has been asked a lot. Did you read these other posts?
Android: What is better - multiple activities or switching views manually?. This link in particular talks about the Android Design Guidelines which "don't mention switching Views at all; it's centered around an Activity-as-View design."
Android - Should I use multiple activities or multiple content views
Android app with multiple activities
How to pass the values from one activity to previous activity
I'm not sure what you mean by the back button not always going back to the right screen correctly. I've got a game with a similar structure to yours, and the back button always takes the user correctly up the chain of activities.
Furthermore, using the onResume, onPause etc is somewhat necessary. What happens to your application if the phone rings? (Yes I know, some people still do strange things like using their phone to receive calls! :P) The OS tries to call the onPause method in your activity, but if that isn't implemented then your application won't act as expected. Another useful thing with onResume is it lets you update your tables as soon as the user returns to the view. For example, your player has just completed a level and is then brought back to the select difficulty screen. If you simply recover the previous screen from memory, it might not have been updated to take into account that a the level was just completed. However, if you put some code in onResume to handle that, then that will always be executed before the player sees the screen.
Lastly, you say transferring data around activities is a pain with intents - yes, that's probably true. But I usually find transferring any kind of complex data a pain, no matter how you do it. Are intents really that much worse, or is it just that things aren't as easy as you'd hoped? I don't mean any offense with that; I also often find things which intuitively seem easy to be rather frustrating to implement in code.

Why is there no cancel button in Android's progress dialogs?

I'm facing a head-scratching moment similar to what this person (from Jan 2008) experienced when I realized that there is no cancel button in Android's progress dialog or spinners. It is now July 2009 and I've just installed the cupcake version of Android. Has this thing changed? If not, are you adding a cancel button into the dialogs and how do you do it?
not sure about the whole cancel button...i've heard reports of the onCancel() method not firing properly. my solution just consists of making a normal button on the dialog with a call to return whenever the button is pressed.
private void createCancelProgressDialog(String title, String message, String buttonText)
{
cancelDialog = new ProgressDialog(this);
cancelDialog.setTitle(title);
cancelDialog.setMessage(message);
cancelDialog.setButton(buttonText, new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
// Use either finish() or return() to either close the activity or just the dialog
return;
}
});
cancelDialog.show();
}
then just use a simple call method from elsewhere in your activity
createCancelProgressDialog("Loading", "Please wait while activity is loading", "Cancel");
rather simple solution, but it does the trick ;)
also just to note that cancelDialog is an activity wipe variable, if you dont need to call it from elsewhere, then you should be able to get away with just limiting the scope of the variable to that method.
I'm no Android user or developer, but I think the answer given in the linked-to thread is pretty decent: there's a hardware "back" key on all Android devices. Users are supposed to know to press Back to back out of whatever activity they're currently in.
Thus, the UI designers feel it's unnecessary to include a GUI back/cancel button. This could be viewed as the UI application of the DRY principle; if there's already one way of doing something, that's enough.
The hardware key is the answer here. I'd be careful about generalising the DRY principle to UIs. There are plenty of cases where you need to hammer, hammer, hammer the same point to the user repeatedly via headings, body text, colours and images.
Users dont "read" UIs the way you read a novel. They scan read.
I can't speak for other apps, but in mine anything that might cause the UI thread to wait is executed in a seperate thread. The most I'll do is show a small progress spinner in the titlebar to let the user know something is going on in the background.
As an Android user, and developer, I can say, in my opinion, and based around my understanding of the platform, that there is a good reason for not having a cancel button by default on the cancel-free progress dialogs.
As a developer, these dialogs can not be cancelled by default, that is, you have to explicitely set them as cancelable.
This makes sense, because their purpose is to alert the user, via the UI thread, that some work is going on elsewhere that is important to the updating of the UI thread, before the user should continue their use of the application.
For example, when fetching a list of data to occupy an empty screen, there is nothing to interact with, and the user needs to be made aware that something is going on, and to expect there to be nothing available to interact with until this process is complete.
However, there may be cases, such as data retrieval from the internet, where the process is "sketchy" and due to connectivity issues, you may not be able to complete the request, and get stuck here.
This as a develop is where you enable the dialog to be cancel-able.
Now as a user, one that clearly understands the UI paradigm of Android, I know that if I want to go back to what I was doing before, or "cancel" the current operation, I should hit the back key.
As a user, it's no different to knowing that in Android, the menu key can often reveal hidden options, some times on a seemingly blank screen with no way to interact.
This is the behaviour a user expects, and the way the platform is designed. You could very well add a cancel button, but from a users perspective that understands their phone, this makes no difference, the back key is clearly the intended choice for this purpose.
Activities and UIs have a flow, you flow through activities and UI "screens" which are "stacked" and the back button essentially "pops" the last thing off the stack to return you to where you were previously. If you see the dialog as another of these activities, you want to pop it from the top of the stack to return to what is underneath, or an activity previous to that.
If a developer has a dialog that can not be cancelled by back, it is either, by design, for which there can, in cases, be very good reason for, or, it is poor development and an oversight on the devloper's part.

Categories