I have a fragement with various Spinners in it. To have these spinners display an initial (non selectable) value I'm using a custom arrayAdapter (SpinnerHintAdapter). The only important thing this class does is override the getCount() so the last item of the selection-array isn't displayed, this is where the initial value is stored.
This all works fine untill you rotate the device, then the spinners are set on the last normal value of the list for some reason, even though the Fragment class still thinks it's set on the initial value.
Anyone any clue why this happens and / or how to solve this problem?
Code samples:
fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_free_top_up, container, false);
Spinner pet = (Spinner) rootView.findViewById(R.id.pet);
SpinnerHintAdapter<CharSequence> petAdapter = SpinnerHintAdapter.createFromResource(getActivity(),
R.array.pet_array, android.R.layout.simple_spinner_item);
petAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
pet.setAdapter(petAdapter);
pet.setSelection(pet.getCount());
return rootView;
}
SpinnerHintAdapter:
#Override
public int getCount() {
int count = super.getCount();
// The last item will be the hint.
return count > 0 ? count - 1 : count;
}
example string-array
<string-array name="pet_array">
<item>Yes</item>
<item>No</item>
<item>(initial value)</item>
</string-array>
The activity is re-created when you rotate the device. You need to override onSavedInstanceState function, save your data in a Bundle and then use that data in onCreate again.
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//save your data
}
and then use that in your onCreate to restore your spinner.
Haven't found an answer to this specific problem but used another method to display an initial value which can be found here: https://stackoverflow.com/a/12221309/3453217
Any clarification as to what went wrong with my first attempt is still welcome.
Related
I have two fragment.In first one is EditText witch taking some info from user,and in another fragment having Spinner.I am sending EditText,that was first converted to String, to Spinner in second fragment with Android Navigation.And it shows text just fine but when i wish to add another text to Spinner it just replace first text and showing only one text/item in spinner. So my question how to have save text that i previously sent and have nice drop down spinner. BTW i am using data binding i don't know if it is important information for solutions.
fragment from where i am sending data:
#Override
public void onClickComplete() {
String addCategoryString = addCategory.getText().toString();
FluctuatingCategoryFragmentDirections.ActionFluctuatingCategoryToCreatingBudgetingItem2 action
= FluctuatingCategoryFragmentDirections.actionFluctuatingCategoryToCreatingBudgetingItem2();
action.setFluctuatingCategory(addCategoryString);
navController.navigate(action);
}
receiving fragment:
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (getArguments() != null) {
CreatingBudgetingItemFragmentArgs args = CreatingBudgetingItemFragmentArgs.fromBundle(getArguments());
String getCategory = args.getFluctuatingCategory();
Log.i("Category: ", getCategory);
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, android.R.id.text1);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(spinnerAdapter);
spinnerAdapter.notifyDataSetChanged();
}
}
When you are navigating to FluctuatingCategoryToCreatingBudgetingItem, the fragment is getting recreated every time, that's why only the value you passed will show up. You must use ViewModel.
Setup a list which will contain the FluctuatingCategoryies and setup your CreatingBudgetingItemFragment to get list item from ViewModel.
Not giving you any code because I want you to start your learning curve. This
might help.
Here's my situation, I have 1 Activity, and 2 Fragments. When the Activity is started it loads a Fragment, let's call it list, into the view (in a FrameLayout filling the screen). List simply lists blogs posts which are retreived via an async HTTP call to out web api. When a blog is clicked in list is runs this method in the Activity
public void loadBlog(FragmentBlog.Blog theBlog) {
blog = theBlog;
FragmentBlogDetails d = new FragmentBlogDetails();
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_BLOG, theBlog);
d.setArguments(bundle);
fragmentManager.beginTransaction().replace(R.id.container, d).addToBackStack("Detail").commit();
}
Then the blog loads up and is displayed fine. Now, once I press the back key, my list Fragment calls onCreateView again and I have to run the async call again to populate my list, which reset the scroll to the top too. How can I keep my data for needing to be fetched again, and keep the view so the scroll stays where it is?
Here's my onCreateView method in the list Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d("FragmentBlog", "onCreateView");
View view = inflater.inflate(R.layout.fragment_blog, container, false);
list = (ListView) view.findViewById(R.id.the_list);
list.setOnItemClickListener(this);
if (baa != null) {
list.setAdapter(baa);
}
text = (TextView) view.findViewById(R.id.my_text_view);
pb = (ProgressBar) view.findViewById(R.id.progressBar);
asyncForBlogs();
return view;
}
If you need any other bits of code let me know and I'll post them.
You need to save index of last item user clicked. Define a index variable in your fragment and when users clicked on an item store item index in it. Then save data in onSaveInstanceState Call back. You can save other data like this. But make sure the data you put inside bundle is not so big. Do not put any images or videos here!
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("index",index);
super.onSaveInstanceState(outState);
}
after saving your data you can access it in onCreate method. like this
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
index = savedInstanceState.getInt("index");
listView.smoothScrollToPosition(index);
}
}
smoothScrollToPosition scroll your list view to the last place you were been.
I'm using a ViewPager for a multiple-choice exam app, which chooses out randomly thirty questions out of a bigger set. I do this in the PageAdapter that is supplying the pages to the ViewPager.
The problem is that when an orientation change occurs, not only the pager but also the adapter gets reloaded - I know how to save the current pager position but when the adapter gets reset, it also chooses new questions from the set. What would be the proper way to handle this?
Also, side question - what would be the best way to register the choices on the RadioGroups? Directly by click or in a different way?
I'm fairly new to the Android app developement.
Activity:
public class MyActivity extends SherlockActivity {
ActionBar actionBar;
ViewPager pager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pager = new ViewPager(this);
setContentView(pager);
QuestionsAdapter adapter = new QuestionsAdapter(this);
pager.setAdapter(adapter);
int position = 0;
if (savedInstanceState != null) {
position = savedInstanceState.getInt("Q_NUMBER");
}
pager.setCurrentItem(position);
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
int position = pager.getCurrentItem();
savedInstanceState.putInt("Q_NUMBER", position);
}
}
Adapter:
class QuestionsAdapter extends PagerAdapter {
Context context;
QuestionsHelper dbQuestions;
boolean exam;
List<HashMap<String,Object>> examQuestions;
public QuestionsAdapter(Context context, boolean exam) {
this.context = context;
this.examQuestions = GetQuestionsFromDB(30);
}
public Object instantiateItem(View collection, int position) {
LayoutInflater inflater = (LayoutInflater) collection.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view;
HashMap<String,Object> q;
view = inflater.inflate(R.layout.exam_question_layout, null);
q = getQuestion(position+1);
((TextView)view.findViewById(R.id.q_number)).setText(Integer.toString(position+1)+".");
((TextView)view.findViewById(R.id.q_question)).setText(q.get("question").toString());
((RadioButton)view.findViewById(R.id.q_answer_a)).setText(q.get("answer_a").toString());
((RadioButton)view.findViewById(R.id.q_answer_b)).setText(q.get("answer_b").toString());
((RadioButton)view.findViewById(R.id.q_answer_c)).setText(q.get("answer_c").toString());
((ViewPager)collection).addView(view, 0);
return view;
}
}
Screen Rotation will redraw the entire screen in the new orientation, we can prevent it with overriding configuration changes.
add android:configChanges="orientation|screenSize" under your screen declaration in Android Manifest
android:configChanges="orientation|screenSize"
And Override onConfigurationChanged(Configuration) in your activity like
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
I know how to save the current pager position but when the adapter
gets reset, it also chooses new questions from the set. What would be
the proper way to handle this?
Your questions should really have an id to uniquely identify them. I'm not sure how you get them from the database but when that would happen you would need to store their ids. Also:
Your adapter should have a long array(or integer) holding 30 values representing the ids of the current selected batch of questions
You'll need to implement the following logic in the adapter: if the long array from the previous point is null then assume it's a clean start and get a new set of 30 questions.
If the long array is non null then we are facing a restore from a configuration change and you'll need to use those ids to get the proper questions from the database instead of a random batch
In the Activity you'll save the long array of the adapter in the onSaveInstanceState() method(savedInstanceState.putLongArray)
In the onCreate method of the Activity, when you create the adapter, you'll check the savedInstanceState Bundle to see if it is non-null and it has the long array and set that on the adapter(so it will know which questions to get)
what would be the best way to register the choices on the RadioGroups?
Directly by click or in a different way?
You could use the above method, or create a custom class with Parcelable like it has already been recommended to you in the comments.
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)
}
I am using loaders to load data into a gridview . First let me show you some code:-
The Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle icic){
View v=inflater.inflate(R.layout.thegrid, container, false);
final GridView gv = (GridView)v.findViewById(
R.id.thegridview);
gv.setAdapter(upAd);//upAd is the array adapter initialized in oncreate of the fragment
gv.setOnItemClickListener(this);
Bundle args=new Bundle();
args.putString("page", String.valueOf(page));
getLoaderManager().initLoader(1, args, this);
return v;
}
#Override
public Loader<ArrayList<HashMap<String, String>>> onCreateLoader(int loaderId,Bundle thebundle) {
if(loaderId==1){
String page=thebundle.getString("page");
return new MyLoader(getActivity().getApplicationContext(),page);
}
return null;
}
#Override
public void onLoadFinished(Loader<ArrayList<HashMap<String, String>>> theLoader,ArrayList<HashMap<String, String>> data) {
upAd.addAll(data);
}
Now when the fragment is displayed , it shows a grid of 10 elements downloaded.Now when the screen orientation is changed, the loader behaves well and does not download the data again but it adds the same data again to the grid.. so i end up having a grid of 20 elements the same 10 repeated twice. Well i can call :-
upAd.clear()
in onLoadFinished but i want to avoid that.Could someone tell me what would I be doing wrong here.
You have 20 items because ArrayList holding data in upAd already has 10 items from past. Then you are calling upAd.addAll(data);. That means you are adding another 10 identical items to that array list. So the solution is to clear adapter.
use this line in your Activity declaration in menifiest file.
android:configChanges="orientation|keyboardHidden"