Android - Fragment is null after Picture Intent (REQUEST_PICTURE_CAPTURE) - android

I have an app which has a PagerAdapter with a few Fragments.
From the main Activity, I start a REQUEST_PICTURE_CAPTURE intent, it works ok, when it ends it calls onActivityResult().
In onActivityResult(), I have simple code:
m_PagerAdapter.GetTheFrag().DoStuff(..., ...);
Where m_PagerAdapter = derived from a PagerAdapter,
GetTheFrag() = // returns the derived Fragment object, which wasn't null before starting the Intent. This is the problem,
DoStuff() ==> never gets called, because m_PagerAdapter.GetTheFrag() is null .
The weird thing is that sometimes it does work and sometimes not, doesn't matter what picture quality or any capture related properties.
class MyPagerAdapter extends PagerAdapter {
// In PagerAdapter
public MyFrag GetTheFrag() {
return m_MyFrag;
}
// In PagerAdapter
private View getViewByID(Integer integerID) {
switch (integerID) {
case R.layout.my_frag:
if (m_MyFrag == null) {
m_MyFrag = new MyFrag(m_MainActivity);
}
return m_MyFrag.GetMyFragView();
}
return null;
}
}
// In MyFrag
public View GetMyFragView() {
return m_ThisView;
}

Most likely, your process was terminated while the camera app was in the foreground. A fresh process was created for you as part of returning control to your app after the camera finishes. A fresh activity will have been created, along with fresh fragments. However, whatever m_MyFrag is, it is not being filled in by your code when this occurs, and so it is null.
You will be encountering the same sort of problem if the user leaves your app (e.g., via HOME), Android terminates your process, but then the user returns to your app within 30 minutes or so of having left. So, while you happen to be experiencing it as part of launching a third-party camera app, the same problem will occur elsewhere.

Related

Why do you check for savedInstanceState == null when adding fragment?

In the fragment doc, in one of the example, they check for savedInstanceState == null when adding a fragment:
public static class DetailsActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getResources().getConfiguration().orientation
== Configuration.ORIENTATION_LANDSCAPE) {
// If the screen is now in landscape mode, we can show the
// dialog in-line with the list so we don't need this activity.
finish();
return;
}
if (savedInstanceState == null) {
// During initial setup, plug in the details fragment.
DetailsFragment details = new DetailsFragment();
details.setArguments(getIntent().getExtras());
getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
}
}
}
What is the purpose of this check? What would happen if it is not there?
What is the purpose of this check?
To not add the fragment twice, though I prefer checking to see if the fragment is there instead of relying on that Bundle being null.
What would happen if it is not there?
Initially, nothing, as the Bundle will be null when the activity is first created.
However, then, the user rotates the device's screen from portrait to landscape. Or, the user changes languages. Or, the user puts the device into a manufacturer-supplied car dock. Or, the user does any other configuration change.
Your activity will be destroyed and recreated by default. Your fragments will also be destroyed and recreated by default (exception: those on which setRetainInstance(true) are called, which are detached from the old activity and attached to the new one).
So, the second time the activity is created -- the instance created as a result of the configuration change -- your fragment already exists, as it was either recreated or retained. You don't want a second instance of that fragment (usually), and therefore you take steps to detect that this has occurred and not run a fresh FragmentTransaction.

Switching between ActionBar.Tabs issue

I have implemented application which downloads data from web and then shows it in two ActionBar.Tabs. I have one issue with it. If I switch from one tab to other one, application starts another download and while download is not finished, the app freezes. If internet connection is slow it becomes very annoying. I've decide to add ProgressDialog to app to show user that application is downloading data from web. I've added a piece of code which implements ProgressDialog to AsyncTask which performs the download, but that didn't help. I understand why that happens but can't find the way how to fix it :(
Data which will be fitted into tab is represented as instance of Fragment class. This instance after creation will be added to transaction, and only after adding mFragment object to transaction, app switches to another tab.
This is the part of tabListener code:
// ...
#Override
public void onTabSelected(Tab tab, FragmentTransaction transaction) {
if (mFragment == null) {
/*
* Creation of ParkFragment is the reason why app locks!! Because in
* ParkFragment data is being downloaded from web
*/
mFragment = new ParkFragment(mUrl, mActivity);
/*
* mFragment can't be added to transaction until downloading is
* finished, that's why app doesn't switch fast
*/
transaction.add(android.R.id.content, mFragment, mTag);
} else {
transaction.attach(mFragment);
}
}
Please, if anybody has any ideas how to implement ProgressDialog to avoid the delay in switching between tabs, share with it.
Thank you for reading.
Updated: I've read the answer to this question but didn't understand how that implementation will help me managing delays: Changing Tabs is Slow/Laggy - Using Fragments.
UPD:
public class ParkFragment extends ListFragment {
private ArrayList<Cinemas> cinema;
private CinemasAdapter cinemaAdapter;
private String url;
private Activity activity;
public ParkFragment (String cinema,Activity activ){
url = cinema;
activity = activ;
}
public void onCreate(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
cinema = new Handler().handle(url,activity);
cinemaAdapter = new CinemasAdapter(activity, R.layout.movie_data_row, cinema);
setListAdapter(cinemaAdapter);
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
Cinemas movie = cinemaAdapter.getItem(position);
Intent intent = new Intent (activity, More.class);
intent.putExtra("Cinemas", movie);
intent.putExtra("data", movie.getBitmap());
Bundle translateBundle =
ActivityOptions.makeCustomAnimation(activity,
R.anim.slide_in_left, R.anim.slide_out_left).toBundle();
startActivity (intent, translateBundle);
}
}
In your ParkFragment you can use content providers combining with loaders. Downloading data should be handled in a service, when done the service inserts data into DB via content providers. And in your fragment, the loader will load the data.
There is one app named API Demos in any Android emulators, which has several examples related to content providers/ loaders/ services… The source code of its is available in Android SDK, at: [Android SDK]/samples/android-x/ApiDemos, in which x is API level.
I just think so, but if you could share your code of ParkFragment, perhaps there would be another problem?
Edited
In your ParkFragment, you can create a ResultReceiver (available in API 3+), put it into an Intent and start a service to download/ handle the url. The service keeps the instance of the ResultReceiver, when done it sends back the downloaded data to your fragment via send(int, Bundle). A Bundle can hold primitive data types such as String, int, byte[]… For more complex data, you create a class to hold it which implements Parcelable or Serializable.
Or with Bound Services, you can call the service's methods directly from within the fragment. Note that services run on main UI thread, so to avoid of NetworkOnMainThreadException, you need something like Thread in your service.

Handling onNewIntent in Fragment

I am writing an application that uses NFC to read some data stored on it. My application uses Fragments and Fragment don't come with onNewIntent() method. Since, the data I am reading is done with my separate class which handles NFC related operation, the only thing I need to do is update the TextView inside the Fragment. However this implementation can also be used to pass new Intent to the Fragment.
Here is my current implementation which makes use of an interface. I am calling the listener after new Intent is received and NFC related checks succeeds. This is the FragmentActivity which hosts Fragment.
public class Main extends FragmentActivity implements
ActionBar.OnNavigationListener {
private Bundle myBalanceBundle;
private NFC nfcObj;
private NewBalanceListener newBlanceListener;
#Override
public void onNewIntent(Intent intent) {
setIntent(intent);
}
#Override
protected void onResume() {
getNFCState();
super.onResume();
}
private void getNFCState() {
//Other NFC related codes
else if (nfc_state == NFC.NFC_STATE_ENABLED){
readNFCTag();
}
}
private void readNFCTag() {
//Other NFC related codes
if (getIntent().getAction().equals(NfcAdapter.ACTION_TECH_DISCOVERED)) {
nfcObj.setTag((Tag) getIntent().getParcelableExtra(
NfcAdapter.EXTRA_TAG));
nfcObj.readQuickBalance();
transitQuickReadFragment(nfcObj.getCurrentBalance());
}
}
private void transitQuickReadFragment(String balance) {
// Creates a balance bundle and calls to select MyBalance Fragment if it
// is not visible. Calls listener is it is already visible.
if (actionBar.getSelectedNavigationIndex() != 1) {
if (myBalanceBundle == null)
myBalanceBundle = new Bundle();
myBalanceBundle.putString(Keys.BALANCE.toString(), balance);
actionBar.setSelectedNavigationItem(1);
} else {
newBlanceListener.onNewBalanceRead(balance);
}
}
#Override
public boolean onNavigationItemSelected(int position, long id) {
// Other fragment related codes
fragment = new MyBalance();
fragment.setArguments(myBalanceBundle);
newBlanceListener = (NewBalanceListener) fragment;
// Other fragment related codes
}
// Interface callbacks. You can pass new Intent here if your application
// requires it.
public interface NewBalanceListener {
public void onNewBalanceRead(String newBalance);
}
}
This is MyBalance Fragment which has TextView that needs to be updated whenever NFC is read:
public class MyBalance extends Fragment implements NewBalanceListener {
private TextView mybalance_value;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//Other onCreateView related code
Bundle bundle = this.getArguments();
if (bundle != null)
mybalance_value.setText(bundle.getString(Keys.BALANCE.toString(),
"0.00"));
else
mybalance_value.setText("0.00");
//Other onCreateView related code
}
#Override
public void onNewBalanceRead(String newBalance) {
mybalance_value.setText(newBalance);
}
}
This code works perfectly like expected for my application but, I want to know if there is better way to handle new Intent from Fragments?
This is an old question, but let me answer it in case anybody bumps into it.
First of all you have a bug in your code:
You can't register Fragments as listeners inside Activity the way you do it. The reason is that Activity and Fragments can be destroyed by the system and re-created later from saved state (see documentation on Recreating an Activity). When this happens, new instances of both the Activity and the Fragment will be created, but the code that sets the Fragment as a listener will not run, therefore onNewBalanceRead() will never be called. This is very common bug in Android applications.
In order to communicate events from Activity to Fragment I see at least two possible approaches:
Interface based:
There is an officially recommended approach for communication between Fragments. This approach is similar to what you do now in that it uses callback interfaces implemented by either Fragment or Activity, but its drawback is a tight coupling and lots of ugly code.
Event bus based:
The better approach (IMHO) is to make use of event bus - "master component" (Activity in your case) posts "update" events to event bus, whereas "slave component" (Fragment in your case) registers itself to event bus in onStart() (unregisters in onStop()) in order to receive these events. This is a cleaner approach which doesn't add any coupling between communicating components.
All my projects use Green Robot's EventBus, and I can't recommend it highly enough.
There is at least one alternative: From Activity.onNewIntent documentation:
An activity will always be paused before receiving a new intent, so you can count on onResume() being called after this method.
Note that getIntent() still returns the original Intent. You can use setIntent(Intent) to update it to this new Intent.
FragmentActivity.onNewIntent documentation is different but I don't think it contradicts the above statements. I also make the assumption that Fragment.onResume will be called after FragmentActivity.onResume, even though the documentation seems a little fussy to me, though my tests confirm this assumption. Based on this I updated the Intent in the activity like so (examples in Kotlin)
override fun onNewIntent(intent: Intent?) {
setIntent(intent)
super.onNewIntent(intent)
}
And in Fragment.onResume I could handle the new intent like so
override fun onResume() {
super.onResume()
doStuff(activity.intent)
}
This way the activity don't need to know about what fragments it holds.
No, there is no better way. Fragments can live longer than Activities and are not necessarily tied to them at all so providing new intents would not make sense.
Btw, you have a few bugs in your code :)
if (actionBar.getSelectedNavigationIndex() != 1) {
Magic numbers are bad! use a constant.
if (myBalanceBundle == null)
myBalanceBundle = new Bundle();
myBalanceBundle.putString(Keys.BALANCE.toString(), balance);
actionBar.setSelectedNavigationItem(1);
we already know that the navigationitem is set to 1
} else {
newBlanceListener.onNewBalanceRead(balance);
Add a null check. The user might have never selected a navigation item.

Android Activity Tests - Testing Restarts

In both the Testing Fundamentals and the Activity Testing section entitled "Adding state management tests" in the Android developer documentation, it suggests testing activity restarts using:
mActivity.finish();
mActivity = this.getActivity();
Having tried this with the addition of a sleep between the two statements above, I can see that the Activity is not redrawn on the screen when the mActivity = this.getActivity() is executed. My test appears to work, but I am intrigued as to why the Activity isn't redrawn on the screen as this doesn't seem to be mentioned in the API documentation.
I'd be grateful for any insight into this anyone can give. At the point the finish() method is called, the Activity disappears from the screen, but doesn't reappear when the this.getActivity() is called. I've also tried putting an mActivity.setVisible(true) after the getActivity(), but that doesn't help.
My code snippet is now:
...
mActivity.finish();
Thread.sleep(5000);
mActivity = this.getActivity();
Thread.sleep(5000);
...
I've searched extensively, but can't find any explanation of why the Activity doesn't reappear when getActivity() is called.
I've tested this on Android 2.3.5, 2.3.3 and 2.2.2 all with the same result.
It seems that class ActivityInstrumentationTestCase2 needs an additional finish method in which some cleanup must be done. In meanwhile you can work around this problem by cleaning up yourself after finishing the activity. So change your code as follows:
mActivity.finish();
setActivity(null);
mActivity = this.getActivity();
This can be explained as follows. Method getActivity in class ActivityInstrumentationTestCase2 calls setActivity(a)
public T getActivity() {
Activity a = super.getActivity();
if (a == null) {
// set initial touch mode
getInstrumentation().setInTouchMode(mInitialTouchMode);
final String targetPackage =
getInstrumentation().getTargetContext().getPackageName();
// inject custom intent, if provided
if (mActivityIntent == null) {
a = launchActivity(targetPackage, mActivityClass, null);
} else {
a = launchActivityWithIntent(targetPackage,
mActivityClass,
mActivityIntent);
}
setActivity(a);
}
return (T) a;
}
Method setActivity sets internal variable mActivityIntent.
public void setActivityIntent(Intent i) {
mActivityIntent = i;
}
All calls after this first call will now use the new value mActivityIntent instead of a null-value. As a result
a = launchActivityWithIntent(targetPackage, mActivityClass, mActivityIntent);
will be called. Probably your app can not be started with this intent.
Note that method rearDown does a proper cleanup:
protected void tearDown() throws Exception {
// Finish the Activity off (unless was never launched anyway)
Activity a = super.getActivity();
if (a != null) {
a.finish();
setActivity(null);
}
}

Is checking savedInstanceState for null in onCreate a good way to tell if the device was rotated?

There's stuff I'd like to do in my Activities' onCreate() method only if they're constructed the first time, not when the device was rotated (on configuration changes). Currently I'm checking the savedInstanceState parameter passed into onCreate() for this. If it's null, then it's the first time the Activity starts, else there was only a rotation.
Is this a good and reliable way to tell this? Are there alternatives to this?
I don't know of a better solution. Romain Guy describes the same approach (checking savedInstance state or other objects you pass for null).
In the new activity, in onCreate(), all you have to do to get your
object back is to call getLastNonConfigurationInstance(). In
Photostream, this method is invoked and if the returned value is not
null, the grid is loaded with the list of photos from the previous
activity:
private void loadPhotos() {
final Object data = getLastNonConfigurationInstance();
// The activity is starting for the first time, load the photos from Flickr
if (data == null) {
mTask = new GetPhotoListTask().execute(mCurrentPage);
} else {
// The activity was destroyed/created automatically, populate the grid
// of photos with the images loaded by the previous activity
final LoadedPhoto[] photos = (LoadedPhoto[]) data;
for (LoadedPhoto photo : photos) {
addPhoto(photo);
}
}
}
When I am lazy to do this, I just disable recreating the Activity on orientation change. As described at How do I disable orientation change on Android?

Categories

Resources