How to update view state in Android? - android

I need to refresh text in TextView (TextView with id) on configuration change. Android saves its state on configuration state because it has id. But I want to update text (because language may be changed).

I solved a similar problem in this way... add a static boolean to the class containing the view you want to refresh, and change this variable to true when opening settings... then, update your onResume method in this way:
#Override
public void onResume(){
super.onResume();
if (changed == true) {
this.recreate();
changed=false;
}
}
this will force recreation on the view.

Related

How to preserve AutoCompleteTextView's DropDown state when gets back from launched Activity

Currently, when I
Launch a new Activity by clicking on AutoCompleteTextView's drop down
Close the launched Activity
AutoCompleteTextView's drop down is hidden.
I would like to preserve AutoCompleteTextView's drop-down state which includes
Drop down should not be hidden when gets back from launched Activity
Drop down's scroll position should be preserved.
I'm not exactly sure the reason why AutoCompleteTextView's dropdown will be hidden when I back from launched Activity. Hence, I had tried 2 things
Change windowSoftInputMode of launched Activity from stateAlwaysHidden to stateUnchanged.
In onActivityResult, when the launched Activity is closed, perform mSearchSrcTextView.showDropDown(); explicitly.
However, I am still facing the issue. The previous scroll position of AutoCompleteTextView's dropdown is not preserved. It is reset back to top of the list.
Here's the screen-shot to better illustrate the problem I am facing.
(Current AutoCompleteTextView's dropdown is scrolled to the end. I click on the last item and launch a new Activity)
(New Activity is launched. Now, I click on the BACK soft key twice, to close the keyboard and then close the Activity)
(Due to the explicit call of mSearchSrcTextView.showDropDown(); in onActivityResult, the drop down is shown again. However, its previous scrolled position is not being preserved. Start of list is being shown instead of end of list)
I was wondering, is there any way to preserved the AutoCompleteTextView's DropDown state, when closing a previous launched Activity?
For AutoCompleteTextView, it has a method called dismissDropDown(). I believe when back from newly launched activity, this function is being triggered. So we workaround this problem by extending AutoCompleteTextView & override it's dismissDropDown().
We add a boolean flag temporaryIgnoreDismissDropDown, to indicate whether to temporarily ignore dismissDropDown.
public class MyAutoCompleteTextView extends AutoCompleteTextView {
private boolean temporaryIgnoreDismissDropDown = false;
.....
#Override
public void dismissDropDown() {
if (this.temporaryIgnoreDismissDropDown) {
this.temporaryIgnoreDismissDropDown = false;
return;
}
super.dismissDropDown();
}
public void setTemporaryIgnoreDismissDropDown(boolean flag) {
this.temporaryIgnoreDismissDropDown = flag;
}
}
Before launching new Activity, we set dismissDropDown to true. After coming back from launched activity, dismissDropDown is called. The override method checks if temporaryIgnoreDismissDropDown is true, just set it to false & do nothing. So the real dismissDropDown is skipped.
// myAutoCompleteTextView is instance of MyAutoCompleteTextView
myAutoCompleteTextView.setTemporaryIgnoreDismissDropDown(true);
// launch new Activity
startActivity(....);
Hope this help, good luck!
After an hour of coding, much trying and a lot of googling around, I've put together a solution that does just what you want. It uses reflection to access the ListView within the Dropdown menu and to access the dropdown state when you leave the activity.
The code for this is kinda long, so I'll walk you through all the parts. Firstly, I have some variables we will need:
boolean wasDropdownOpen;
int oldDropdownY;
Handler handler;
The handler will be neccessary for later, as we have to do a little trick in the onResume() method. Initialize it as usual in your onCreate() method:
handler = new Handler(getMainLooper());
Now, let's get to the tricky part.
You need to call the following method before you start any activity. It can't be done in onPause() since the Dropdown menu is already closed when this method is called. In my test code I've overridden the startActivity() and startActivityForResult() method, and called it there, but you can do this however you like.
private void processBeforeStart() {
ListPopupWindow window = getWindow(textView);
if(window == null) return;
wasDropdownOpen = window.isShowing();
ListView lv = getListView(window);
if(lv == null) return;
View view = lv.getChildAt(0);
oldDropdownY = -view.getTop() + lv.getFirstVisiblePosition() * view.getHeight();
}
This will save your dropdown ListView's state for later. Now, we will load it. This is the onResume() method we will need for this:
#Override
protected void onResume() {
super.onResume();
if (wasDropdownOpen)
textView.showDropDown();
handler.postDelayed(new Runnable() {
#Override
public void run() {
ListView lv = getListView(getWindow(textView));
if (lv != null)
scrollToY(lv, oldDropdownY);
}
}, 150);
}
First of all, let me explain this method. We saved the state if the dropdown was open, so we reopen the menu if it was. Simple. The next part is the scrolling. We need to do this in a Handler because the UI is not yet fully loaded when onResume() is called and therefore the ListView is still inaccessible.
The scrollToY() method you see there is a modified version of the code from this post, as Android's ListView does not have an inbuilt method to set the scroll position as precisely as we want it here.
The implementation of this method is as follows:
private void scrollToY(ListView lv, int position) {
int itemHeight = lv.getChildAt(0).getHeight();
int item = (int) Math.floor(position / itemHeight);
int scroll = (item * itemHeight) - position;
lv.setSelectionFromTop(item, scroll);// Important
}
Now, you've probably seen the getWindow() and getListView() methods I've used above. These are the reflection methods, which we have to use because Android does not expose a public API to access the ListView within the ListPopupWindow of the AutoCompleteTextView. Additionally, the DropDownListView, a subclass of ListView that is actually used within this object, is not visible to the oudside as well, so we have to use Reflection once again.
Here is the implementation of my two helper methods:
private ListView getListView(ListPopupWindow window) {
for (Field field : window.getClass().getDeclaredFields()) {
if (field.getType().getName().equals("android.widget.DropDownListView")) {
field.setAccessible(true);
try {
return (ListView) field.get(window);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return null;
}
private ListPopupWindow getWindow(AutoCompleteTextView tv) {
Class realClass = tv.getClass().getName().contains("support") ? tv.getClass().getSuperclass() : tv.getClass();
for (Field field : realClass.getDeclaredFields()) {
if (field.getType().getName().equals(ListPopupWindow.class.getName())) {
field.setAccessible(true);
try {
return (ListPopupWindow) field.get(tv);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
return null;
}
I've tested this on Android O (API level 26) and it works just as you described you want it to work.
I hope that the effort I put into this answer gets me a chance on the Bounty ;-)
It sounds like you've already figured out how to show the drop-down on demand (via showDropDown()), so I'll only address how to restore the scroll position of the dropdown.
You can access the first visible position of the dropdown like this:
autocomplete.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int firstVisiblePosition = parent.getFirstVisiblePosition();
// save this value somehow
}
});
Save the value of this int however you'd like (in memory, via onSaveInstanceState(), pass it through to the started activity so that it can pass it back via onActivityResult(), etc). Then, wherever you re-show the dropdown, do this:
autocomplete.showDropDown();
autocomplete.setListSelection(firstVisiblePosition);
The shortcoming of this technique is that it makes the item at firstVisiblePosition completely visible, so if it was halfway scrolled out of view, the list position won't be restored perfectly. Unfortunately, I don't believe there's any way to save/restore this partial-view offset.

android - RecyclerView items: how to save properties of Views in every row?

My problem is to understand how RecyclerView works.. I have RecyclerView with a little bit complicated item in every row, but the main thing is that Item has a child ImageView and LinearLayout. I want to press ImageView and set Visibility of LinearLayout to GONE or VISIBLE and rotate my ImageView. I tried to do this in my onBindViewHolder:
holder.mIVExpandBtn.setOnClickListener(new OnClickListener() {
boolean isOpen = false;
#Override
public void onClick(View v) {
if (isOpen) {
CounterListAdapter.this.notifyItemChanged(position);
holder.mLLDetails.setVisibility(GONE);
holder.mDivider.setVisibility(VISIBLE);
holder.setArrowUp(false);
isOpen = false;
counterItem.setDetailsOpened(false);
} else {
holder.mLLDetails.setVisibility(VISIBLE);
holder.mDivider.setVisibility(GONE);
holder.setArrowUp(true);
isOpen = true;
counterItem.setDetailsOpened(true);
}
}
});
And I have some problems here.
I have a boolean variable inside OnClickListener, I know its wrong, so it changes only one time when I expand my LinearLayout. If I make this boolean global variable, if I expand one row of RecyclerView isOpen = true for any other item and it doesn't expand itself when I click on ImageView.. Where should I place this boolean?
And the second question - how can I save the state of my RecyclerView rows on screen rotation? For example I expanded one of my rows, LinearLayout.setVisibility(VISIBLE), change screen orientation and its closed.
For your first problem, you should put your boolean variable where you also define your views, i.e., inside your ViewHolder, ir order that onClick you call the boolean this way
if(holder.isOpen)
In this way you keep the reference of each boolean to each row.
For your second problem, the solution is pretty simple. In your manifest, in the activity where you have your RecyclerView, define the following:
android:configChanges="keyboardHidden|orientation|screenSize"
This prevents your activity from being recreated on configuration change in case you rotate the screen, so the activity will keep it's state and your RecyclerView will therefor not be recreated along with your adapter.
Notice that this means that, if your activity is not recreated, onPause, onStop, etc, will not run. This is only for screen rotation, your activity will still run the method onConfigurationChanged() which is where you should define any changes you need in case the screen rotates.
You better put the OnClickListener in the Holder class, something like this:
private class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
#Override
public void onClick(View view) {
....
}
About how to save state I think all things that define the rows should be saved in the array you pass to the adapter contructor, you can add fields in the array item object that save VISIBILITY state of the row views.
On screen rotation then two options:
1 - as #Ricardo said avoiding Activity recreation
2 - onSaveInstanceState / onRestoreInstanceStates save/restore the array that define the rows .. my prefered method for that is to use JSON and put it in a String that can be saved/restored in the Bundle.

Android Spinner remove OnItemSelectedListener

Others has the problem as doesn't working, I have the problem it is working ( and it shouldn't )
I have a data model, which is saved, and need to loaded back to GUI, Activity. It has a few spinner value.
The data is place to a common accesible class, a reference holder.
The activity's onCreate it will check if is on edit mode or not with:
editMode = getIntent().getBooleanExtra(EDIT_MODE_KEY, false);
It will load the UI elements from xml, and start selecting, filling values.
At editing mode, and at creation mode it should select values what has the data model.
At runtime ( after onResume() ) has some workflow: is something is selected at spinner1, than should refresh the spinner2 adapter content and so on.
It doesn't worked the plain .setSelection(positiontoSelect); so I have added a delayed post, now is working.
My problem is: I would like remove for temp the selection listener, call the selection and add back the listener.
Here is the code, which should be modified:
if (editedTimezonePosition > -1) {
final int positiontoSelect = editedTimezonePosition;
new Handler().postDelayed(new Runnable() {
public void run() {
OnItemSelectedListener listener = spSelectTimezone.getOnItemSelectedListener();
spSelectTimezone.setOnItemSelectedListener(null);
spSelectTimezone.setSelection(positiontoSelect);
spSelectTimezone.setOnItemSelectedListener(listener);
}
}, 250);
}
setting to null the listener has no effect: I am getting callback to my listener method.
If you have any idea how to fix it, please share it!
You could put a counter variable in your onItemSelected method. If it is 0 (meaning the first time the method has been called), do nothing but increment the variable. If it is greater than 0, execute the rest of your code.
private int mSpinnerSelectionCount=0;
public void onItemSelected(AdapterView<?> parent, View view,
int pos, long id) {
if(mSpinnerSelectionCount == 0){
mSpinnerSelectionCount++;
} else {
// Your normal selection code here
}
}

EditText Settext not working with Fragment

I have fragments for 3 states of a screen; Add, Edit and View.
In Add, I create an entity and save it.
Next time I open it in View mode and set the entity name using
EditText entityName = (EditText) view.findViewById(R.id.entityName);
entityName.setText(entity.getEntityname());
I click on the edit button from View mode to open the Edit mode. I change the entity name here and save it. This brings me back to the view screen. But I find the entity name is not updated.
I debug and found that entity.getEntityname() is having correct value. I am not sure why the edit text does not take new value.
Any ideas?
Note: I am using android version 2.2
The EditText appears to have an issue with resetting text in onCreateView. So the solution here is to reset the text in onResume. This works.
Also there's an issue in onActivityCreated. I reset edittext's content in onStart and it works. [credits to #savepopulation]
There are some classes of View in Android should save their status when their container is detached.
Fragment.onViewCreated() should be called before View.onSaveInstanceState(). So if you set a value in the method Fragment.onViewCreated(). The value should be cleared in the method View.onRestoreInstanceState(Parcelable state).
For example,class TextView,RecyclerView and so on.You can read the code of TextView.java:
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
// Save state if we are forced to
final boolean freezesText = getFreezesText();
boolean hasSelection = false;
int start = -1;
int end = -1;
....
if (freezesText || hasSelection) {
SavedState ss = new SavedState(superState);
....
}
....
}
There are to params to control whether to save the state: "freezesText" and "hasSelection".
TextView can't be selected,so hasSelection is false. the function ,getFreezesText(),return false in class TextView too.
So,TextView would not save the state.
the code of EditText.java:
#Override
public boolean getFreezesText() {
return true;
}
EditText return true,so EditText should save the state.
There some method to fix this bug:
1.implement EditText.getFreezesText() and return false,and clear the state of select in EditText
2.implement onSaveInstanceState of EditText, return null.like this:
public Parcelable onSaveInstanceState() {
super.onSaveInstanceState();
return null;
}
3.use EditText.setSaveEnable(false);
4.add param in xml " saveEnable='false'"
As mentioned earlier, the EditText appears to have an issue with resetting text in onCreateView.
This is because once a fragment is created , till the time we remove it from the backstack, its method onResume would be called as the view is not created again.
So the solution here is to reset the text in onResume. This will work on all times even if u lock and unlock ur screen while that fragment is open or you are coming back from another fragment
However if you are setting this data from a bundle it is better tonsave that value in an instance variable cause the bundle might come null amd u can gett null pointer issues then
According to the #TusharVengrulekar , this is how you must implement your Fragment
public class ActionBar extends Fragment {
private TextView lbl_title;
private String title;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_action_bar, container, false);
title = "Contacts";
lbl_title = (TextView) view.findViewById(R.id.lbl_title);
return view;
}
#Override
public void onStart(){
super.onStart();
lbl_title.setText(title);
}
#Override
public void onResume(){
super.onResume();
}
}<!---->
Also there's an issue in onActivityCreated. I reset edittext's content in onStart and it works.
onResume() or onStart() is fine for resetting the text on the EditText on popBackStack() but the issue is when the app goes into the background either of them will be triggered that would not be the expected behavior from the application. We can do something like this too, to reset the text on EditText-
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
binding.coolEt.setText("xyz")
}
This will work on fragment 100%
override fun onResume() {
super.onResume()
Handler(Looper.getMainLooper()).postDelayed({
editText.setText("Abc")
}, 500)
}

Android - Spinner : how may I distinguish user's actions from computer's actions in a OnItemSelectedListener

I'm in a trouble managing a spinner, so may I ask for your help ?
I have a spinner with its adapter.
I initialize the spinner with a list of values when starting my activity.
Then I force the selected value to be the one used in the object that I manage.
Once the screen is initialized :
When the user selects a value in the spinner, according to the selected value, I may continue (or not) to another activity for let the user choose a complementary and necessary value.
If the user "cancels" this second activity, I want to rollback the spinner to its previous selected value, and cancel some actions made in the meantime.
If the user goes to the end of the second activity, everything is fine and I want juste to refresh the spinner display with the datas selected in the second activity (I overload the getView method in the adapter to do this).
Overall, I can easily do all of this, however, when I force the selected value in the spinner at the begining of my activity, or whene returning back from the second activity by "Cancel", the change value event is catched and the second activity is triggered (the user did not click anything at all).
How would you allow the second activity to be lauched only if the change of the selected value in the spinner is due to a manual action from the user, and prevent that same second activity to be launched when the value of spinner is changed "in the code "?
I tried many solutions, as setting a boolean into the adapter that tells if the next event will be raised because of an "in the code" action.
Or also putting a boolean in the adapter that tells if the adapter has initialised itself, and I force that boolean to true on the forst change catched event.
But nothing that really works fine.
Thank you for your help.
Oliver
I've always solved that issue with boolean flags, it isnt pretty at all, but it works if you think it through.
The idea is more or less, create a global usable boolean and init with false, in the onSelectedItemListener() use that boolean to choose wether or not to trigger the action, the important thing to remember is to set it to true after the computer has selected it the first time automatically, and reset it to false in the onResume() method.
This isnt perfect but it should work.
Edit:
bool spinnerUsable1;
bool spinnerUsable2;
int positionSpinner;
public void onCreate(Bundle savedInstanceState){
spinnerUsable1 = false;
spinnerUsable2 = true;
if(savedInstanceState != null){
positionSpinner = savedInstanceState.getInt("posSpinner");
if(positionSpinner != 0) spinnerUsable2 = false;
}
//Declare your spinner, set the on item selected lister
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
boolean spinnerUsable = (spinnerUsable1 && spinnerUsable2);
if (!spinnerUsable1) {
spinnerUsable1 = true;
} else if (!spinnerUsable2) {
spinnerUsable2 = true;
}
if (spinnerUsable) {
//Action;
}
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// Nothing
}
});
}
Something like this should work.

Categories

Resources