I have an odd issue which I've never seen anywhere on SO so I've resorted to posting here, hoping I make it clear enough.
I have a simple SherlockFragmentActivity as shown further down which contains three fragments which all call getActivity().setTitle() in their onCreateOptionsMenu() allowing my app to change titles depending on which fragment is visible.
This works as desired, but for some reason (perhaps unrelated) when I exit my application by means of the HOME button occasionally the title isn't visible upon reopening the application. It seems that should I close my app and reopen it, it's fine but after leaving it for a while the title won't be there when I reopen it.
I have absolutely no idea what could be causing this so any help is appreciated. The layout of my application (relevant to this issue) is a basic splash screen (as an activity) with a loading bar which then opens the following FragmentActivity:
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import android.os.Bundle;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import static java.lang.Math.*;
public class FragmentControl extends SherlockFragmentActivity {
private static final int NUM_PAGES = 3;
private ViewPager mPager;
private PagerAdapter mPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_control);
ActionBar action = getSupportActionBar();
action.setDisplayShowTitleEnabled(true);
action.setDisplayShowHomeEnabled(false);
mPager = (ViewPager)findViewById(R.id.pager);
mPagerAdapter = new FragmentControlAdapter(getSupportFragmentManager(), NUM_PAGES);
mPager.setAdapter(mPagerAdapter);
// If this activity wasn't called after a reload
if((Integer)getIntent().getExtras().get("current") == null){
// Always start on the middle page, or as close as possible
mPager.setCurrentItem((int) ceil(NUM_PAGES/2));
// Otherwise start on the page we left for a smoother experience
} else {
mPager.setCurrentItem((Integer)getIntent().getExtras().get("current"));
}
}
}
Only when the application re-opens to the FragmentActivity do I see this issue, when reopening on anything else and navigating to this activity it's fine (as you'd expect).
Any and all help is appreciated, hope I've made it clear.
Oh, and if it matters I'm currently targeting API 17 with minimum support for API 8. The test phone I'm seeing this issue on is a HTC One S - not sure on other devices yet but I'm going to start looking.
occasionally (...) after leaving it for a while
This sounds like your application process is killed in the meantime.
Make sure to save instance state (like what title is displayed) using onSaveInstanceState and restore it in Activity.onCreate or Fragment.onViewCreated.
Related
I have an ExpandableListView that loads well the first time but then after the adapter values are changed and notifyDataSetChanged called (I even tried setting the adapter again) the ExpandableListView on screen stays the same... however getCount() does change. Before posting the code, let me explain my intention and what I've tried so far. I want to generate the data for the adapter's variables when page 2 in the ViewPager is shown, and then show it in the ExpandableListView inside that same fragment. I managed to do this in several different ways but there's always a problem that makes me look for an alternative.
First I made all of this in the ViewPager's default class, MainActivity, using addOnPageChangeListener instead of SimpleOnPageChangeListener, and made the variables related to the ExpandableListView static, so I could set the adapter from MainActivity. The data was generated flawlessly everytime the specific page was shown, but there was a problem: the ExpandableListView wasn't showing anything, even tho all the variables involved had the correct values and the adapter was set.
So I took another road... I made the ViewPager static, put the listener for it in page 2 fragment class and generated data+set adapter from there, no static variables involved with the exception of the ViewPager. This works flawlessly for the first swipe to the page, data is generated and shown successfully, but in subsequent swipes to the page something weird happens: the code inside the listener is called x times each time, with x being the number of times the user has swiped to the fragment. This means first time it calls the code once (good), then two times second time and so on... this obviously messes up everything. I tested and this doesn't happen when the listener is in MainActivity, the code there is called only once everytime (but ExpandableListView is not showing anything). In this stage first swipe is perfect but second also delivers the expected result, updating the ExpandableListView and everything but doing so much work than necessary (because of running the code once), then at third swipe I get a NullPointerException (no surprise given the amount of messing it does).
At this point I get very frustrated, it seems everytime I use a static variable it doesn't work correctly: first with the ExpandableListView related variables used to set the adapter from MainActivity, then with making the ViewPager static and using a listener from it to run a method in another class.
Next step was reading for a substitute to addOnPageChangeListener to see if that worked, that's when I met SimpleOnPageChangeListener. I was happy, it worked... it run the code only once with every swipe to the Page while also showing the result in the ExpandableListView but, problem... it doesn't update it even tho it's data changes successfully (current stage).
So I'm kind of new with programming in general and don't have a clue of what's going on here... if it's that I'm discovering a bunch of bugs, something messed up with my installed SDK or if there's something else I'm missing. Please help.
Simplified version of my current stage:
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class ResultsFragment extends android.support.v4.app.Fragment {
ExpandableListView resultsELV;
HashMap<Header,List<String>> resultsHash = new HashMap<Header, List<String>>();
List<Header> resultsTitles = new ArrayList<>();
ResultsAdapter resultsAdapter;
View rootView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.fragment_results, container, false);
resultsELV = (ExpandableListView)rootView.findViewById(R.id.expandableListVieww);
ViewPager.SimpleOnPageChangeListener listener = new
ViewPager.SimpleOnPageChangeListener(){
#Override
public void onPageSelected(int position) {
if (position == 1) {
getResults();
}
}
};
MainActivity.mViewPager.setOnPageChangeListener(listener);
return rootView; }
public void getResults() {
// resultsHash & resultsTitles get filled
Toast.makeText(resultsELV.getContext(),resultsHash.size()+"
"+resultsTitles,Toast.LENGTH_SHORT).show();
resultsAdapter=new ResultsAdapter(resultsELV.getContext(),resultsHash,resultsTitles);
resultsELV.setAdapter(resultsAdapter);
}
}
For instance, the mainActivity.java file is really clustered and to keep it clean i created a second .java(class) where i will execute some code upon a button press. I cannot figure out how to do it at all. And i am not sure what search terms to use either so i apologize if this has been covered.
Heres what i have in my "test" application.
I have a main activity with a single button on it.
package com.test.secondclass;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
Button startButton;
final Intent second = new Intent(getApplicationContext(), testClass.class);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startButton = (Button)findViewById(R.id.button1);
startButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
startActivity(second);
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
Now here is the "second" class that i made, now remember this is very short i am just using this for an exercise program before implementing it into my actual program.
package com.test.secondclass;
import android.app.Activity;
import android.widget.Toast;
public class testClass extends Activity{
public void onCreate(){
Toast.makeText(getApplicationContext(), "Second class thinger started", Toast.LENGTH_LONG).show();
}
}
And if i try this i get a force close immediately. If i comment out the "intent" part at the very beginning of my main activity then the program runs. But it doesnt do what i want. obviously. Thanks everyone
Add an OnClickListener to send whatever information to testClass, as shown below (untested):
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent secondIntent = new Intent();
secondIntent.setClassName(myPackageName, "testClass");
startActivityForResult(secondIntent, REQUEST_CODE);
});
References here and here.
Here's my viewpoint: you are receiving a forced close due the fact that you is trying to open user interface methods (such the Toast) with no context.
IMPORTANT: I'm assuming that you already defined both classes in the manifest.xml file!
Before explaining, I'll make a brief:
Toast: this class opens a quick message, receiving as main arguments the context, the message and the time-to-show;
The context: it is the "environment" where to show. Something like a visual scope, that defines the resources you have. In most cases, you can setup it with setContentView method.
Issue Explanation, in my opinion: The "crash" occurs because you opens the Toast message with no context. An activity is a UI control very similar to a viewpage. If you call a new activity, its very like to call a new page, and so, a new context. In the seccond activity, I haven't see any context. I think that you was assuming that the context is preserved from the first activity, but it ins't because its a new activity.
How to fix:
In the seccond class, define a layout view with setContentView, or...
Reimplement your seccond class as a Service, and call it through startService, or...
Define an AIDL mechams (similar to previous fix, but more sophisticated and complex, as it enables async method calls).
Hope it has helped in some way.
Thank you ALL for the answers!! I was actually able to do what i wanted by using the "stopSelf()" command after i displayed the Toast message. I implemented a service class and when i press the button the testClass.java class gets called and runs the "toast" message then immediately exits by the "stopSelf()" command. I made sure of this by including an "onDestroy()" method which also displayed a simple toast message confirming that the service was stopping :). I usually do stuff like this using threads but it was making the main activity really messy no matter how much formatting i did. So i wanted to have a seperate class i could use.
And to the commenter EfEs, i come from programming in C# language for windows. Android is a new playground for me and im still learning. i think im doing quite well but wasnt sure how to do what i asked. But i figured it out then. And thanks for clearing up that an Activity in android is like a "WindowsForm" in C# where it is completely new GUI for the user. I didnt know that. But thanks to all for helping me with your posts!
this is my first app for android, I've been an iPhone dev for 3 years, it's been a change of mindset, and I still find some things odd. First I don't know if this iPhone background might be causing some troubles, but here's what I'm trying to do:
I want to implement an ActionBar with two options working like a TabBar from iOS:
What I want is that when the user selects an action, some Activity will be presented to the user.
Here's what I'm doing so far (which isn't that much):
I'm Using ActionBarSherlock, I have 3 activities: myApp, First and Second
myapp.java only creates the ActionBar elements and loads the activity_first:
package com.example.myApp;
import com.actionbarsherlock.ActionBarSherlock;
import com.actionbarsherlock.view.MenuItem;
import android.os.Bundle;
import com.actionbarsherlock.ActionBarSherlock.OnCreateOptionsMenuListener;
import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.view.Menu;
public class myApp extends SherlockActivity implements OnCreateOptionsMenuListener {
ActionBarSherlock mSherlock = ActionBarSherlock.wrap(this);
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTitle("myApp");
mSherlock.setContentView(R.layout.activity_first);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add("First")
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
menu.add("Second")
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
return true;
}
}
The activities have nothing so far, apart from the generated stub:
import android.app.Activity;
import android.os.Bundle;
public class First extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
}
}
So I have 1 Question and 1 problem:
Question: Is this the correct use of an ActivityBar? I mean, should it be used to switch activities?
Problem: As you can see in the OnCreate method of myApp class, I'm loading the activity_first, It does load the activity, however it loads the ActionBar Twice, like so:
I don't get why is it being loaded twice. If I remove the line: mSherlock.setContentView(R.layout.activity_first); it loads the Bar once, obviously I need to load the activity...
Also I've assigned the NoActionBar theme to the activity_first in the graphical editor of the XML (activity_first.xml), but it doesn't work. What Can I do to load it just once.
Thank you for your valuable time.
Is this the correct use of an ActivityBar? I mean, should it be used to switch activities?
You can think of "First" and "Second" as being the equivalent of toolbar buttons in a desktop app. You are welcome to have those action bar items start up activities if you wish.
As you can see in the OnCreate method of myApp class, I'm loading the activity_first, It does load the activity, however it loads the ActionBar Twice, like so:
Delete this line:
ActionBarSherlock mSherlock = ActionBarSherlock.wrap(this);
and change mSherlock.setContentView(R.layout.activity_first); to just setContentView(R.layout.activity_first);. I believe that will clear up your problems.
I am using ActionBarSherlock on Android 4.0.3, so it might use the native ActionBar.
When I launch my application everything works fine. However, when I go to the Homescreen and wait until its killed (or simply change the system font, then it happens imediately) and then switch by the "last used"-dialog again to the app everything reloads smoothly, except the Actionbar has now empty tabs.
So the tabs are there, but empty (and do not work).
The strange thing is that even in the Application object onCreate is called (as in the TabParentActivity, see code below), so theoretically the Application should have been completely restarted (and not just partially like onResume...).
When I then kill my app (via the task manager) and reopen it the problem has gone.
How I add the Actionbar in my TabParentActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tab_parent);
//Global initialization
...
ActionBar ab = getSupportActionBar();
// set defaults for logo & home up
ab.setDisplayShowHomeEnabled(true);
ab.setDisplayShowTitleEnabled(false);
ab.setDisplayHomeAsUpEnabled(false);
ab.setDisplayUseLogoEnabled(true);
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
...
for(StolScreen s: screensInTabs){
Tab t = mAb.newTab().setText(s.displayName);
t.setTabListener(new NormalTabListener(this.mActivity, s));
mAb.addTab(t);
}
}
How it looks like:
All ok
Now tabs empty
I was now able to solve it by myself.
The reason is really crazy: In the above code I set the label of a tab in the loop with the s.displayName. s is belonging to an Enum called StolScreen.
In there the displayName is initialized via a call in Tools (initialized before), which retrieves the display name from an xml file. What was now actually happening when I was returning to the activity (and only then), was that StolScreen was loaded (in an Enum the fields are loaded like static members) BEFORE Tools was initialized.
So just an empty String was put on the tabs :D. Anyway, thx 4 help ;)
Does anyone know the rationale behind the design of Android to destroy and re-create activities on a simple orientation change?
Shouldn't allowing the activity to just redraw itself (if it so chooses) be a better, simpler and more practical design?
BTW, I'm well aware of how to disable my app from orientation change effects, but what I don't really get is the reason for this design in Android
In docs,
http://developer.android.com/guide/topics/resources/runtime-changes.html
it states that,
The restart behavior is designed to help your application adapt to new configurations by automatically reloading your application with alternative resources.
Dont know exactly why, my guess is because it has to reconstruct the activity on the screen.
OnCreate takes in a variable "Bundle savedInstanceState". You can save the data in there for faster reloading.
package com.android.hello;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class HelloAndroid extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextView = new TextView(this);
if (savedInstanceState == null) {
mTextView.setText("Welcome to HelloAndroid!");
} else {
mTextView.setText("Welcome back.");
}
setContentView(mTextView);
}
private TextView mTextView = null;
}
This is pretty simple. Android destroys and recreates an activity on orientation changes so that you (the developer) have the option to keep on using the previous layout or use and implement another one.