How to restore state of an Android View (UI)? - android

I'm trying to save state of my view in one activity and pass it to another activity using a Bundle. In the second activity I try to restore the view state using the bundle.
First Activity
private View CreateView() {
ScrollView scrollView = new ScrollView(this);
final LinearLayout layout = new LinearLayout(this);
layout.setOrientation(android.widget.LinearLayout.VERTICAL);
scrollView.addView(layout);
Button btn = new Button(this);
btn.setId(100);
btn.setText("Button Text");
btn.setOnClickListener(new ClickListener());
layout.addView(btn);
return scrollView;
}
onCreate
super.onCreate(savedInstanceState);
View v = CreateView();
setContentView(v);
Saving state
SparseArray<Parcelable> array = new SparseArray<Parcelable>();
view.saveHierarchyState(array);
Bundle bundle = new Bundle();
bundle.putSparseParcelableArray("state", array);
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtras(bundle);
startActivity(intent);
Second Activity onCreate
bundle = this.getIntent().getExtras();
View view = new View(this);
view.restoreHierarchyState(bundle.getSparseParcelableArray("state"));
setContentView(view.getRootView());
Button btn = (Button)findViewById(100);
Everything works without an exception. However, I face two issues:
1. The view in second activity is blank. Though I've restored the saved state I can't see anything
2. Instance for the button (with id 100) in second activity is always null
While debugging I can see one of the values in the bundle having an id 100
Any help on what I seem to be doing wrong will be appreciated. Thanks

I figured out that it is not possible to restore a view in a different activity from which it was initially created (or rendered). Since View is not a serializable type, it can't be send in its entirety as well. At this point there doesn't seem to be any solution (I haven't explored option of modifying Android source code)

Why are you trying to create a View programatically and sending it to another activity? You could simply use the same layout in both activities and then only pass the data that backs the view. That too there would be more convenient ways than using a parcelable?
Could you elaborate on what you are trying to achieve here? Maybe we can give you a better response then....

Have you tried doing this in onSavedInstanceState(...)?
From the Android documentation:
onSaveInstanceState(Bundle) is called before placing the activity in
such a background state, allowing you to save away any dynamic
instance state in your activity into the given Bundle, to be later
received in onCreate(Bundle)

The only approach I've taken with this is using the onSaveInstanceState and onRestoreInstanceState.
public void onSaveInstanceState(Bundle outState){
//save the state of the sign up views!
Serializable page0fields = sign_in_page.getFields();
outState.putSerializable("page0fields", page0fields);
super.onSaveInstanceState(outState);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState){
//restore the state of the sign up views!
sign_in_page.fillFields(
savedInstanceState.getSerializable("page0fields")
);
super.onRestoreInstanceState(savedInstanceState);
}
On the other hand, you seem to desire a much more automated approach to this. I don't believe you can pass around views arbitrarily, as they aren't serializable, though I may be mistaken.

Related

android - replace only content in the same activity

I have a MainActivity, which contains ImageView, TextView and 3 clickable Buttons.
After clicking the button, I want to change something in SQlite dtb and according that load different data, but show it again in the same activity.
public void ClickBtn(View v)
{
//insertData(String...
Intent intent = new Intent(MainActivity.this, MainActivity.class);
startActivity(intent);
}
So generally - in Main Activity.js I am getting the data from ID, which was clicked before and show that data. The MainActivity should be used infinity times to show different data.
The layout will be always the same - ImageView, TextView and 3 clickable Buttons, just the text will be different.
The question is, how can I only change content inside the same Activity?
I don't think Intent intent = new Intent(MainActivity.this, MainActivity.class); from the current activity can open the same activity...
You really need to study the basics.
When you are working in android, XML layout files are merely blueprints which ultimately are parsed into a reflection-created anonymous view instance, which contains as children each of the members of the XML layout, with the valid XML tag parameters applied to them. Therefore, you aren't dealing with 'Layouts', but with java/kotlin objects, which can be:
Referenced
Mutated
Replaced
So, if you want to change the contents, the first steps is to keep a reference to each object: ImageView, TextView and Buttons, and move the code in charge of filling them to a new method, so you can call it either when loading the activity (onCreate), or when clicking the button. That way the same activity can perform the same action over and over.
Finally, constant recreation of an activity is a TERRIBLE idea. For every object you generate (and an activity IS an object, like everything else), you need X+Y memory, where X is the sum of all the members of the object's class, and Y is the sum of all the operations necessary for instantiation, so by recreating the activity constantly, you waste the device resources, with the added problem of generating a huge backstack of identical activities.
Take a look at a java book, then a kotlin one. It will make your life easier.
This is how I solved it. Just replacing text without refreshing activity. Tested it hundred times also via Memory monitoring and absolutely no impact on device memory.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//get from dtb
int room = 1; int a1 = 2; int a2 = 3; int a3 =4;
TextView views = findViewById(R.id.text1);
views.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//get from dtb - img, text where room = a1;
TextView vv = findViewById(R.id.textof);
vv.setText("text from dtb");
}
});
TextView view2 = findViewById(R.id.text2);
view2.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//get from dtb - img, text where room = a2;
TextView vv = findViewById(R.id.textof);
vv.setText("another text from dtb");
}
});
}

How to set different data onto a fragment/viewpager

I've looked around but couldn't find any solutions, so this is my last resort.
I'm working on a Xamarin-Android project and I've got a viewpager with one fragment. The trick with this fragment is that even though it's only one fragment, it loads many instances of this one fragment, depending on how many I need but the problem is that each fragment needs to load one object (one set of data). The problem I have is that when I iterate through the list of returned items (loaded from a file), it obviously loops through everything and sets the last set of returned data onto my fragment. This causes me to have many fragments with the same data. What I need is to load one set of data onto each fragment instead of it loading the last set onto my fragment. So in essence, I have one fragment which loads many instances and each instance needs to show one object's data. How do I do this?
Thanks in advance for any assistance.
Okay, please see below - This is the fragment class. I've left out the OnCreateView of the fragment, as it only inflates the fragment resource and gets the textviews etc. Let me know if you need the FragmentPagerAdapter code as well. This one fragment has many instances, which is set in the FragmentPagerAdapter class in the Count and GetItem overidden methods. Count returns the number of instances required and GetItem which does "return ThisFragment.newInstance(position);"
EDIT: Code updated with solution
private int mNum;
private string code, status;
TextView textviewMyObjectCode, textviewMyObjectStatus;
public static ThisFragment newInstance(int num)
{
ThisFragment myFragment = new ThisFragment();
MyObject myObject = new MyObject();
List<MyObject> myObjectList = MyObjectIO.LoadMyObjectsFromFile();
myObject.MyObjectNumber = myObjectList[num].MyObjectNumber;
myObject.MyObjectStatus = myObjectList[num].MyObjectStatus;
args.PutInt("num", num);
args.PutString("objectCode", myObject.MyObjectNumber);
args.PutString("objectStatus", myObject.MyObjectStatus);
myFragment.Arguments = args;
return thisFragment;
}
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
mNum = Arguments != null ? Arguments.GetInt("num") : 1;
code = Arguments.GetString("objectCode");
status = Arguments.GetString("objectStatus");
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
if (container == null)
{
return null;
}
View thisView = inflater.Inflate(Resource.Layout.object_fragment, container, false);
textviewObjectStatus = thisView.FindViewById<TextView>(Resource.Id.textviewObjectStatus);
textviewObjectCode = thisView.FindViewById<TextView>(Resource.Id.textviewObjectCode);
textviewObjectCode.Text = code;
textviewObjectStatus.Text = status;
return thisView;
}
It should be the responsibility of the 'FragmentPagerAdapter' to create new Fragments. You seem to have delegated this responsibility to a Fragment class which is not the right approach. Here you are setting text of 'textviewMyObjectCode' and 'textviewMyObjectStatus' again and again in a loop so these values will get over-ridden in every iteration of the loop.
Ideally you should access 'MyObjectList' in 'newInstance' and set the values in Bundle objects as per the index passed in 'num'. Also 'newInstance' should be part of 'FragmentPagerAdapter' and 'MyObjectList' should be available to 'FragmentPagerAdapter'.
If 'ThisFragment' is the Fragment which is needed to be part of ViewPager then that fragment should just do the job of retrieving its data from the Bundle and set the needed data to its resources.
I managed to figure it out. Since I am using the newInstance method, which contains a position variable called "num" and because I get a list of objects from the LoadObjectsFromFile method, I can assign a specific object to a specific fragment. Example: I can load object[0] onto fragment[0]. I do this in the newInstance method, set the values to a Bundle and then retrieve it later in OnCreate. Then in OnCreateView, I can place the values onto my textviews. #Jay, I now realize what you were saying all along. It didn't click initially :)

Android: getApplication() is undefined for type View.OnClickListener()

I am currently trying to implement the log in facility for Facebook within my android app. I am encountering a problem with
Intent diaryIntent = new Intent(getApplication(), DiaryListActivity.class);
With the error The method getApplication() is undefined for the type new View.OnClickListener. I have looked at other questions regarding this problem however I haven't managed to find a solution. Personally I believe it might be something to do to the fact I am using fragments, but I am new to this concept so don't understand how to solve the problem, or if it even is something to do with this.
Class
public class SelectionFragment extends Fragment {
private static final String TAG = "SelectionFragment";
private ImageButton mAddButton;
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.selection,
container, false);
mAddButton = (ImageButton) view.findViewById(R.id.diaryform_home_add);
mAddButton.setOnClickListener(new View.OnClickListener() { //Listens for a user to interact with the save button
public void onClick(View v) {
// Do something to save the data
Intent diaryIntent = new Intent(getApplication(), DiaryListActivity.class);
startActivity(diaryIntent);
}
});
return view;
Any guidance is appreciated, thank you in advance.
EDIT: #mike20132013 #codeMagic #crazyPixel All gave me right answers, but I can't give them all the correct answer so I gave the first answer the correct tick. Thanks for the answers though, appreciated.
Change your:
Intent diaryIntent = new Intent(getApplication(), DiaryListActivity.class);
to
Change
Intent diaryIntent = new Intent(getActivity(), DiaryListActivity.class);
Since you are using fragments you have to use getActivity() instead of context.getApplicationContext().
I am pretty sure this will work..Good Luck .. :)
To start the activity you need to use the activity context within the fragment use -
getActivity().getApplicationContext()
instead of
getApplication()
to move on to that activity use -
getActivity().startActivity(intent)
A good thumb rule to remember is that fragments are "inside" activites i.e. their context (and everything related) comes from the activity they resides in.
Change
Intent diaryIntent = new Intent(getApplication(), DiaryListActivity.class);
to
Intent diaryIntent = new Intent(v.getContext(), DiaryListActivity.class);
You need a Context for the Intent Constructor not an application though I think you meant to have getApplicationContext() but, either way, just use the Context of the View clicked.
All About Intents

Android fragment crashes when app comes from background

I'm having a problem that is starting to give me head-hakes.
My application is basically a FragmentActivity with a navigation drawer and each button of the navigation drawer loads a new fragment.
I'm using android.support.v4 for almost every component in the project.
My issue lies every time my app goes to background and comes back to foreground the oncreate view loads the view again and most of the variables that I use to create the view are null and my app crashes because of that.
Can anyone point me in the right direction to solve this problem? would it be because of the OnsavedInstanceState() method, the onCreateView() doing the variable instantiation, or anything else?
Here's my error log:
java.lang.RuntimeException: Unable to start activity ComponentInfo{pt.gema.welcomeangola/pt.gema.welcomeangola.activities.MainActivity}: java.lang.NullPointerException
Caused by: java.lang.NullPointerException
at java.util.ArrayList.<init>(ArrayList.java:93)
at pt.gema.welcomeangola.activities.ListViewExampleFragment.onCreateView(ListViewExampleFragment.java:103)
One of my fragments OnCreateView() Code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
lvef = this;
View rootView = inflater.inflate(R.layout.activity_items_list, container, false);
getActivity().setTitle("ListViewExample");
btnSearch = (ToggleButton) rootView.findViewById(R.id.btn_search);
btnSearchText = (ImageButton) rootView.findViewById(R.id.btn_search_string);
btnAZ = (ToggleButton) rootView.findViewById(R.id.btn_az);
searchText = (EditText) rootView.findViewById(R.id.search_text);
layoutSearch = (RelativeLayout) rootView.findViewById(R.id.search_layout);
pBar = (RelativeLayout) rootView.findViewById(R.id.progress_bar_layout);
List<Integer> filter= new ArrayList<Integer>();
filter.add(id_type);
OriginalObjectsLocality= new ArrayList<ListPlaceInfo>(Objects);
listview = (ListView) rootView.findViewById(R.id.list_item);
adapter = new WAListAdapter(getActivity().getApplicationContext(),Objects,OriginalObjectsLocality,lvef);
listview.setAdapter(adapter);
return rootView;
}
EDIT
Although the problem was not only in the Arraylist Objects, but the instantiation of another class that I tried to access. laalto answer helped me to find the problem, therefore I consider it the right answer.
From comments:
#Szymon I receive that arraylist in the fragment constructor, and i thisnk my problem is here... public ListViewExampleFragment(ArrayList objects) { super(); this.Objects = objects; this.listPlaceAZ=azListing(new ArrayList(objects)); }
You set up a member variable Objects in a constructor that takes an arraylist param.
Fragments must have a parameterless constructor and the framework will create the fragment calling that empty constructor. So your parameter-taking constructor is not called and Objects is left null, causing a NPE here:
OriginalObjectsLocality= new ArrayList<ListPlaceInfo>(Objects);
If you need to pass parameters to your fragments, use a Bundle set with setArguments() and accessed with getArguments().
But passing an arraylist as a Bundle requires a Parceble and that requires me to change almost all my code, is there any other option?
You could make the member variable static so it survives fragment recreation. But I wouldn't recommend that as it will create a whole set of other problems, such as memory leaks due to incollectible objects.
It's better to rethink the design. For example, you probably don't need to pass an array of objects around. You could just pass an array of identifiers as parameter instead, and query the objects by id when needed.

Activity not updating properly

I'm new to android, so maybe I'm doing something horribly wrong. I want to have a particular Activity that shows details about an instance of a "Creature" class for a game. Name, damage taken, that sort of thing.
I'm having a problem getting the creature data to be properly shown in the GUI objects. Both at initial creation (where it should copy the creature's name into the name field) and when a damage mark is added (where it doesn't update to show the proper image).
Here's my mini-example of what I have:
public class CreatureDetailActivity2 extends Activity
{
Creature creature;
public void addMark(View v)
{
// connected to the button via android:onClick="addMark" in the XML
creature.getTrack().addDamage(DamageType.Normal, 1);
refreshDisplay();
new AlertDialog.Builder(this).setTitle(creature.getName())
.setMessage(creature.getTrack().toString()).show();
}
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_creature_detail);
creature = new Creature("Example");
refreshDisplay();
}
public void refreshDisplay()
{
final View creatureDetailView = this.getLayoutInflater().inflate(
R.layout.activity_creature_detail, null);
final EditText nameField = (EditText) (creatureDetailView
.findViewById(R.id.textbox_creature_name));
nameField.setText(creature.getName());
final ImageView damageBox0 = (ImageView) (creatureDetailView.findViewById(R.id.damageBox0));
damageBox0.setImageResource(R.drawable.__n);
// in the full program this does the same for 0 through 9, but this is a sample
// also, in the full program, this is a dynamic lookup for the correct pic
// but again, this is just a sample version.
}
}
Now the problem is that the app will load up and start, but then none of the widgets will update properly. You can click the button, and it'll show the AlertDialog, and the text of the AlertDialog will change, but the textfield in the activity won't be changed, and the ImageView doesn't change at any point from what it starts as to the one it's supposed to change to.
So I'm very stumped. I can post more about the project's setup if I'm leaving out something important, but I'm not even sure what the problem going on is so I'm not sure what else to include in my question.
final View creatureDetailView = this.getLayoutInflater().inflate(
R.layout.activity_creature_detail, null);
Inflates your Activity's layout into basically nothing, just returning the View it inflated. setContentView is what actually inflates your layout into the Activity's View hierarchy.
Once you inflate your layout you don't need to do it again. Just use findViewById without the reference to a dangling unattached View.
Change your refreshDisplay method to this:
public void refreshDisplay()
{
final EditText nameField = (EditText) findViewById(R.id.textbox_creature_name);
nameField.setText(creature.getName());
final ImageView damageBox0 = (ImageView) findViewById(R.id.damageBox0);
damageBox0.setImageResource(R.drawable.__n);
// in the full program this does the same for 0 through 9, but this is a sample
// also, in the full program, this is a dynamic lookup for the correct pic
// but again, this is just a sample version.
}
Nothing changes because You do it completely wrong.
If You wish to update any view element of current activity You do it like this
View v = findViewById(R.id.element);
v.setText("text");
this is just simple example.
You would need to cast a returned element to correct type like to be able to access all available methods.
What You do wrong is trying to inflate a layout again.

Categories

Resources