I have the following Activity definition:
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/inspectionMainLayout"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:singleLine="false"
android:id="#+id/breadCrumb"
android:visibility="gone">
</LinearLayout>
<ExpandableListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/expandableListView" />
</LinearLayout>
Now in my code I do add buttons dynamically in breadCrumb LinearLayout:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inspection);
LinearLayout mainLayout = (LinearLayout) findViewById(R.id.inspectionMainLayout);
if (mainLayout != null) {
ExpandableListView list = (ExpandableListView) findViewById(R.id.expandableListView);
list.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView expandableListView, View view, int i, int i2, long l) {
LinearLayout breadCrumb = (LinearLayout) findViewById(R.id.breadCrumb);
Button filterButton = new Button(InspectionActivity.this);
filterButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
onFilterButtonClick((Button) view);
}
});
filterButton.setText(item.getFormattedFilter());
breadCrumb.addView(filterButton);
}
}
}
...
}
This code works well, until I do not change the device orientation and my Activity is recreated. Although all the code is executing correctly, screen seems not being updated. Once I restore the previous orientation, all the items suddenly appear. Any idea why and how to fix it?
Thanks
EDIT:
I do think that I'm running into the same problem as describe in this post:
Android: findViewById gives me wrong pointer?
Any idea on how to solve this?
As requested my onRestoreInstanceState:
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
baseCategories = savedInstanceState.getParcelable(BASE_CATEGORIES_STATE);
currentFilter = savedInstanceState.getParcelable(FILTERS_STATE);
}
and on onSaveInstanceState:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(BASE_CATEGORIES_STATE, baseCategories);
outState.putParcelable(FILTERS_STATE, currentFilter);
}
now both of my classes do implement Parcelable interface.
They are persisted and restored correctly.
Still for some resaon the call to the findViewById get's me pointed to the wrong object (not the one that is recreated).
You add views dynamically (on user click event).
By default, android does not "remember" to keep these dynamic views when re-creating the activity on configuration changes, you have to handle this process yourself.
Some possibilities :
Avoid recreating activity on screen rotation by declaring android:configChanges="keyboardHidden|orientation|screenSize" for your activity in AndroidManifest.xml - This is highly not recommended
"Remember" what views were dynamically added when re-creating activity after rotation (for example using extra flags to detect that new filter button was added and pass it via bundle in onSaveInstanceState, and check in onCreate whether you need to re-create the button), or retain the whole view object as explained here
One extra note : you perhaps want to specify "vertical" orientation for your breadCrumb layout, it is horizontal by default.
I found out why this is happening.
onSave/onRestoreInstanceState I was persisting the currentFilter class which has some custom listeners on it.
As onResume method I was doing the following:
#Override
protected void onResume() {
if (currentFilter == null) {
currentFilter = new FilterItemList();
currentFilter.addListener(new FilterItemListListener() {
#Override
public void filterChanged(FilterChangedEvent e) {
filterCategories(categoryRepository);
}
#Override
public void filterAdded(FilterAddedEvent e) {
FilterItem addedItem = e.getAddedItem();
baseCategories.remove(new BaseCategory("", addedItem.getSectionName()));
}
#Override
public void filterRemoved(FilterRemovedEvent e) {
FilterItem removedItem = e.getRemovedItem();
baseCategories.add(new BaseCategory("", removedItem.getSectionName()));
}
});
}
}
The pointer to the previous instance was persisted. That's why my interface was not behaving correctly.
Now I do re-register listeners even when currentFilter is not null (it is restored) so they can point to the right instance.
Is there any pattern in handling this situations?
Thanks
Related
I have a custom object array to show in recycler view and I'm using GridLayout manager for my recylerview and showing (2x4),(3x3) etc. squares.
When user click these squares it's color changed.
But when screen orientate, view is refreshing.
My question is how to keep this data (selected square's color ) when screen orientation ?
Create an interface:
public interface ClickCallback {
void onItemClicked(String id);
}
In your activity create a callback and pass it to your adapter like this:
ClickCallback callback = new ClickCallback() {
#Override
public void onItemClicked(String id) {
//your list item object must have a method to get color
color = yourDataList.get(id).getColor();
}
};
protected void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
bundle.putInt("color", color);
}
YourAdapter adapter = new YourAdapter (yourDataList, callback)
In your adapter don't forget to set OnClickListener and call appropriate method, it could be like this:
public ListItemViewHolder(View itemView) {
super(itemView);
//your code
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
callback.onItemClicked(getAdapterPosition());
}
});
}
}
In OnCreate extract your color data:
public void onCreate(Bundle bundle) {
if (bundle != null) {
value = bundle.getInt("color");
}
}
You can store statuses of each square in a proper format and save them
on onSaveInstanceState and retrieve them on onCreate method and manipulate the adapter as for your requirement.
Read more...
Hope this helps!
"But when screen orientate, view is refreshing."
on screen rotation a new instance of the activity is created
You can have a ViewModel class which stores your arraylist of data including their current state. When activity is recreated via rotation this will be persisted and you will get the correct state.
Ref : https://medium.com/androiddevelopers/viewmodels-a-simple-example-ed5ac416317e
See the following Activity:
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.root);
for (int i = 0; i < 8; i++) {
EditText editText = (EditText) LayoutInflater.from(this).inflate(R.layout.edittextlayout, null);
editText.setText("#" + i);
linearLayout.addView(editText);
}
}
}
The layout R.layout.activity_main:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
and the layout R.layout.edittext_layout:
<?xml version="1.0" encoding="utf-8"?>
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
After starting the app it looks like I would expect: every EditText being filled with it's index.
After rotating the device though, the Activity looks like this:
All the EditTexts are there, but they all contain the same text.
What baffles me even more is that this doesn't happen when creating the EditTexts programmatically with
EditText editText = new EditText(this)
instead of inflating it from a layout.
What's happening there?
You can check out my example and try for yourself here.
EDIT: This is not a duplicate of this question as in my case the text in the EditText does not double but get mixed up between different EditTexts.
Try to set some ID for each View.
For example:
view.setId(id);
Or use
onSaveInstanceState() - onRestoreInstanceState()
for saving info.
The problem is your activity recreates on every orientation and saved instance data handled wrong by Android. You can avoid it by setting an id to your dynamically created views or you can
Override onSaveInstanceState method to save your data like text in your edittexts and recover it on onCreate method like so:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check if we're recreating
if (savedInstanceState != null) {
// Restore value of edittext from saved state
editText.setText(savedInstanceState.getString("edittext"));
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putString("edittext", editText.getText().toString());
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
If you encounter any problem similar after doing this try to Override onRestoreInstanceState method to prevent android from doing your work.
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
//do nothing
}
I'm developing a simple application in which youb have different spots placed on google map.
When I click on a spot I get its details which are displayed in a GridViewPager.
For now my application is based on the GridViewPager sample available with the sdk.
Here is my layout for the spot details (nothing fancy)
<android.support.wearable.view.GridViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"/>
My problem now is that I'm not able to detect a Click event on a card.
I've tried this but it doesn't work.
public class DetailsActivity extends Activity implements GridViewPager.OnClickListener {
#Override
public void onClick(View v) {
}
I've also tried View.OnClickListener.
Have any idea ?
There are two issues here. First, if you really want to make the GridViewPager clickable, you need to tell it to listen for click events - just implementing the OnClickListener interface isn't sufficient. So you need to do something like this:
public class DetailsActivity ... {
#Override
public void onCreate(Bundle savedInstanceState) {
...
GridViewPager pager = (GridViewPager)findViewById(R.id.pager);
pager.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// put your onClick logic here
}
});
...
}
}
That being said, however, based on your description it sounds like what you actually want is to set up click handlers on individual pages within the grid, not on the entire grid. If so, then you'll need to do something similar but in each page's Fragment class. For example:
public class MyPageFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View result = inflater.inflate(...);
result.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// put your onClick logic here
}
});
...
return result;
}
}
Note: if you are using CardFragments in your GridViewPager, then you would probably set the OnClickListener in your onCreateContentView override. Otherwise, the above Fragment-based example should apply.
I'm changing my ContentView of the Activity at some point. (to View2).
After changing it back to View1, the listeners are not more working.
I already tried putting the Listener in the onResume() method.
Is it common anyway to use setContentView() to display e.g. a Progress screen/please wait,...(while an asyncTask is running).
Or should you only have ONE mainView for each Activity? (and replacing the content dynamically).
//EDIT: To be more specific:
I am looking for something like
LinearLayout item = (LinearLayout) findViewById(R.id.mainView);
View child = getLayoutInflater().inflate(R.layout.progress, null);
item.addView(child);
but instead of adding the "progress.xml", it should remove the current layout and ONLY show "progress.xml".
Do I need an "container" and show/hide mainView/progress?
But that doesn't seem very proper to me...
See also code below (stripped)
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view1);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
doSomething();
}
});
}
setContentView(R.layout.view2);
[...]
setContentView(R.layout.view1);
//Listener not more working
Thank you all, for your reply. You made me realize, the onClickListeners are getting lost when I remove or replace (using setContentView()) the main view. I ended up this way now:
onCreate:
setContentView(R.layout.parse);
LinearLayout container = (LinearLayout) findViewById(R.id.container);
container.addView(getLayoutInflater().inflate(R.layout.dialog, null));
container.addView(getLayoutInflater().inflate(R.layout.progress, null));
onStartDoingSomething:
findViewById(R.id.dialog).setVisibility(View.INVISIBLE);
findViewById(R.id.progress).setVisibility(View.VISIBLE);
onEndDoingSomehting:
findViewById(R.id.dialog).setVisibility(View.VISIBLE);
findViewById(R.id.progress).setVisibility(View.INVISIBLE);
I might change View.INVISIBLE to View.GONE, like nmr said, but since I have never used View.GONE, I have to check the Android doku first ;)
Assuming you use 'findViewById' to initialize 'button', you would need to do that every time that you do setContentView(R.layout.view1);
You can only have one UI for each Activity, you should create another activity and in its onCreate method set the content view
Steps:
Create Activity
Use the following code to set the content View:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view2);
}
Define the Activity in the applications manifest as such :
Create an intent in the first Activity:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view1);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
intent();
}
});
}
void intent(){
Intent intent = new Intent();intent.setClass(Activity1.this, Activity2.class);
startActivity(intent);
}
And there you go c:
My code below is based on this: http://web.archive.org/web/20100816175634/http://blog.henriklarsentoft.com/2010/07/android-tabactivity-nested-activities/
My issue is with the history ArrayList that is supposed to store the activities so I can properly utilize the back button, however my app crashes when I hit back. I think it's because history isn't exactly storing Views.
I added:
View view = getLocalActivityManager().startActivity("ViewPagerActivity", new
Intent(this,ViewPagerActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP))
.getDecorView();
to my back method to see if it would load it and it works.
public class FeaturedTabGroup extends ActivityGroup {
// Keep this in a static variable to make it accessible for all the nesten activities, lets them manipulate the view
public static FeaturedTabGroup group;
// Need to keep track of the history if you want the back-button to work properly, don't use this if your activities requires a lot of memory.
private ArrayList history;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.history = new ArrayList();
group = this;
// Start the root activity withing the group and get its view
View view = getLocalActivityManager().startActivity("ViewPagerActivity", new
Intent(this,ViewPagerActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP))
.getDecorView();
// Replace the view of this ActivityGroup
replaceView(view);
}
public void replaceView(View v) {
// Adds the old one to history
history.add(v);
// Changes this Groups View to the new View.
setContentView(v);
System.out.println("view set successful");
}
public void back() {
if(history.size() > 0) {
history.remove(history.size()-1);
setContentView((Integer) history.get(history.size()-1));
}else {
finish();
}
}
#Override
public void onBackPressed() {
FeaturedTabGroup.group.back();
return;
}
}
EDIT:
For brevity, I'll approach this problem with another question: why does setContentView(v) work, but not when the Views are store in an ArrayList? What happens to the view when it is stored in an arraylist?
Can't you work around this issue with ViewSwitcher (http://developer.android.com/reference/android/widget/ViewSwitcher.html)? It provides a cleaner solution and you could always call showPrevious() and showNext() when needed.
setContentView(int) expects a layout resource ID. In your example, can you try to save the view object itself (i.e. without type cast to integer)? As jcXavier says - viewswitcher is a better way to handle this.
Maybe it's late , but here: (history.size() > 0), change 0 to 1, when size is 1, it should just finish.
Adam,
I know it must be late... but perhaps I can help somebody else.
Here is how I solved the problem:
public void back() {
View v;
if(history.size() > 1) {
history.remove(history.size()-1);
v = (View) history.get(history.size()-1);
FirstGroup.group.setContentView(v);
}else {
finish();
}
}