Any idea why the list might be empty?
The code is below.
public class PickFBFriendsActivity extends FragmentActivity {
FriendPickerFragment friendPickerFragment;
// A helper to simplify life for callers who want to populate a Bundle with the necessary
// parameters. A more sophisticated Activity might define its own set of parameters; our needs
// are simple, so we just populate what we want to pass to the FriendPickerFragment.
public static void populateParameters(Intent intent, String userId, boolean multiSelect, boolean showTitleBar) {
intent.putExtra(FriendPickerFragment.USER_ID_BUNDLE_KEY, userId);
intent.putExtra(FriendPickerFragment.MULTI_SELECT_BUNDLE_KEY, multiSelect);
intent.putExtra(FriendPickerFragment.SHOW_TITLE_BAR_BUNDLE_KEY, showTitleBar);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pick_friends_activity);
FragmentManager fm = getSupportFragmentManager();
if (savedInstanceState == null) {
// First time through, we create our fragment programmatically.
final Bundle args = getIntent().getExtras();
friendPickerFragment = new FriendPickerFragment(args);
friendPickerFragment.setUserId(null);
fm.beginTransaction()
.add(R.id.friend_picker_fragment, friendPickerFragment)
.commit();
} else {
// Subsequent times, our fragment is recreated by the framework and already has saved and
// restored its state, so we don't need to specify args again. (In fact, this might be
// incorrect if the fragment was modified programmatically since it was created.)
friendPickerFragment = (FriendPickerFragment) fm.findFragmentById(R.id.friend_picker_fragment);
}
friendPickerFragment.setOnErrorListener(new PickerFragment.OnErrorListener() {
#Override
public void onError(PickerFragment<?> fragment, FacebookException error) {
PickFBFriendsActivity.this.onError(error);
}
});
friendPickerFragment.setOnDoneButtonClickedListener(new PickerFragment.OnDoneButtonClickedListener() {
#Override
public void onDoneButtonClicked(PickerFragment<?> fragment) {
setResult(RESULT_OK, null);
finish();
}
});
}
private void onError(Exception error) {
String text = getString(R.string.exception, error.getMessage());
Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
toast.show();
}
#Override
protected void onStart() {
super.onStart();
}
}
Note that it's pretty much the same as the sample one.
Figured it out: my onStart() method was incomplete, missing the following line:
friendPickerFragment.loadData(false);
Must have deleted it accidently.
Related
I have an authorization app. In that app user have accessToken and refreshToken. I done something like this, that if user login on other hardware than in real hardware he logs out automatically. Imagine I have phone1 where I logged in. And now I'm logging in phone2. When I authorize my tokens changes. So I check in phone1 if tokens are changed than logout automatically. But sometimes after that logout I'm getting error like this Exception :
java.lang.IllegalStateException: Can not perform this action after
onSaveInstanceState.
Here is where I'm log out when tokens are expired.
Note that is a
Call<RefreshTokenActivation> newToken = apiClient.newToken(supportObjToken);
newToken.enqueue(new Callback<RefreshTokenActivation>() {
#Override
public void onResponse(Call<RefreshTokenActivation> call, Response<RefreshTokenActivation> response) {
if (response.isSuccessful()) {
} else {
if (response.code() == 401) {
//Perform this call if refresh token is expired
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
Activity activity = (Activity) context;
MainActivity mainActivity = (MainActivity) activity;
mainActivity.logOut();
}
}, 1000);
}
}
}
If response is 401, that means that my tokens are expired.If tokens are expired, after a second I throw my user to mainActivity.
Here is the code in MainActivity :
public class MainActivity extends AppCompatActivity
implements FragmentChangeListener, TabLayoutLocationInterface {
private ConnectionDetector connectionDetector;
private SlidePageTabsMainFragment slidePageTabsMainFragment;
private MainFragment mainFragment;
private RelativeLayout logOut;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferencesManager.init(this);
setContentView(R.layout.activity_main);
logOut = (RelativeLayout) findViewById(R.id.list6);
connectionDetector = new ConnectionDetector(this);
slidePageTabsMainFragment = new SlidePageTabsMainFragment();
mainFragment = new MainFragment();
connectionEnable();
userLogOut();
}
public void connectionEnable() {
if (!connectionDetector.isConnected()) {
Toast.makeText(this, "Please check your Internet", Toast.LENGTH_LONG).show();
} else {
Boolean loggedIn = SharedPreferencesManager.getInstance().getUserLogged();
if (loggedIn) {
this.replaceFragment(slidePageTabsMainFragment, true);
} else {
this.replaceFragment(mainFragment, true);
}
}
}
#Override
public void replaceFragment(BaseFragment fragment, Boolean isAddToBackStack) {
String backStateName = fragment.getFragmentName();
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.main_fragment_container, fragment, backStateName);
transaction.addToBackStack(backStateName);
transaction.commit();
}
#Override
public int getTabLayoutLocation() {
SlidePageTabsMainFragment slidePageTabsMainFragment = (SlidePageTabsMainFragment)
getSupportFragmentManager().findFragmentByTag("SlidePageTabsMainFragment");
if (slidePageTabsMainFragment == null) {
return 0;
}
return slidePageTabsMainFragment.getTabLayoutLocation();
}
//If user click logOut button
public void userLogOut() {
logOut.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
SharedPreferencesManager.getInstance().setUserLogin(false);
SharedPreferencesManager.getInstance().removeUser();
getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
replaceFragment(mainFragment, false);
}
});
}
//A function for automatic logOut
public void logOut() {
SharedPreferencesManager.getInstance().setUserLogin(false);
replaceFragment(new MainFragment(), false);
getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
recreate();
SharedPreferencesManager.getInstance().removeUser();
}
#Override
protected void onResume() {
super.onResume();
Log.d("Test", "Text");
}
}
I think there is no something hard. So I getting this exception in transaction.commit();
line in replaceFragment() method in MainActivity. In that call you see I'm calling mainActivity.logOut(); and you see in MainActivity the logOut function.
//A function for automatic logOut
public void logOut() {
SharedPreferencesManager.getInstance().setUserLogin(false);
replaceFragment(new MainFragment(), false);
getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
recreate();
}
So in this method I change the SharedPreferences value (that I need for checking on application open does user logged in or not?). After that I'm replacing fragment into mainFragment(that's the base fragment and yes I working on fragments). After that I pop all fragments because after logout if I will click back button, it will go back, so after popping I recreate the app. After recreates it feels like app opened first time. Ok so why it throws exception like this? Any idea?
Used transaction.commitAllowingStateLoss(); instead of transaction.commit();
If you do this than your final state in not allow saved but it is ok if you don't care
For more clarification about commit() and commitAllowingStateLoss() read this blog.
This question already has answers here:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
(17 answers)
Closed 5 years ago.
I'm getting a following error when trying to replace a fragment upon receiving a response from AsyncTask:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
The thing is, I get this error randomly upon restarting my app through Android Studio. In a simplified version my activity contains 4 key methods (onCreate, taskCompleted, parseJSON and fragmentReplace), that determine which fragment should the user see at the start:
private AsyncTask mMyTask;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMyTask = new AsyncTask(this, this);
mMyTask.executeTaskCall("check_user");
}
#Override
public void taskCompleted(String results) {
try {
JSONObject jsonBody = new JSONObject(results);
parseJSON(jsonBody);
}
catch (JSONException je){
}
}
private void parseJSON(JSONObject jsonBody) throws JSONException {
boolean userActive = jsonBody.getBoolean("result");
if (userActive){
fragmentReplace(new FirstFragment(), "FirstFragment");
}
else {
fragmentReplace(new SecondFragment(), "SecondFragment");
}
}
public void fragmentReplace(Fragment fragment, String fragmentTag){
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.layout_container, fragment, fragmentTag)
.commit();
}
What is the reason of this exception happening so random?
You should read WeakReference solution (or may be other also) at java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState.
There is one alternate solution for this problem. Using flag you can handle it, like below
/**
* Flag to avoid "java.lang.IllegalStateException: Can not perform this action after
* onSaveInstanceState". Avoid Fragment transaction until onRestoreInstanceState or onResume
* gets called.
*/
private boolean isOnSaveInstanceStateCalled = false;
#Override
public void onRestoreInstanceState(final Bundle bundle) {
.....
isOnSaveInstanceStateCalled = false;
.....
}
#Override
public void onSaveInstanceState(final Bundle outState) {
.....
isOnSaveInstanceStateCalled = true;
.....
}
#Override
public void onResume() {
super.onResume();
isOnSaveInstanceStateCalled = false;
.....
}
And you can check this boolean value while doing fragment transaction.
private void fragmentReplace(Fragment fragment, String fragmentTag){
if (!isOnSaveInstanceStateCalled) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.layout_container, fragment, fragmentTag)
.commit();
}
}
Update for API 26.1+ (contributed by Stephen M)
Fragment.isStateSaved() has been added since 26.1.0, which can also be used for same purpose.
I discovered a strange behaviour today.
I have my activity which connects to the GoogleApiClient in onStart() and disconnects in the onStop()
The activity uses a GridViewPager to show my fragments. To send messages through the Data Layer i use a callback interface between activity and fragment.
If i call sendMessage() from a button within the Activity layout it works fine. If sendMessage() is executed by the fragment using the callback interface sendMessage() shows the "not connected" Toast.
In both ways the same method in the Activity is called so how is it possible that it behaves different ?
I should mention that the problem only occours after the application is restarted for the first time.
Activity
public class WearPlex extends WearableActivity implements
NavigationRemoteCallbacks,
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private List<Node> nodeList = new ArrayList<Node>();
private List<Fragment> fragmentList = new ArrayList<Fragment>();
private GoogleApiClient googleApiClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wear_plex);
setAmbientEnabled();
fragmentList.add(NavigationRemoteFragment.getInstance(this));
GridViewPager mGridPager = (GridViewPager)findViewById(R.id.gridViewPager);
mGridPager.setAdapter(new MainGridPageAdapter(getFragmentManager(), fragmentList));
googleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
#Override
protected void onStart() {
super.onStart();
googleApiClient.connect();
}
#Override
protected void onStop() {
googleApiClient.disconnect();
super.onStop();
}
#Override
public void onConnected(Bundle bundle) {
Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show();
nodeList.clear();
Wearable.NodeApi.getConnectedNodes(googleApiClient).setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
#Override
public void onResult(NodeApi.GetConnectedNodesResult nodes) {
for (Node node : nodes.getNodes()) nodeList.add(node);
}
});
}
#Override
public void navigationRemoteSendCommand(String commandPath) {
sendMessage(commandPath, null);
}
public void debugOnClick(View view) {
sendMessage("/debug", null);
}
public void sendMessage(String path, byte[] data) {
if (googleApiClient.isConnected()) {
for (int i = 0; i < nodeList.size(); i++) {
if (nodeList.get(i).isNearby()) {
Toast.makeText(this, "Send message", Toast.LENGTH_SHORT).show();
Wearable.MessageApi.sendMessage(googleApiClient, nodeList.get(i).getId(), path, data);
}
}
} else {
Toast.makeText(this, "Not connected", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Toast.makeText(this, "Connection failed", Toast.LENGTH_SHORT).show();
}
Fragment
public class NavigationRemoteFragment extends Fragment {
private static NavigationRemoteFragment navigationRemoteFragment = null;
private NavigationRemoteCallbacks callbackHandler = null;
private ImageButton navBtnCenter;
public static NavigationRemoteFragment getInstance(NavigationRemoteCallbacks handler) {
if (navigationRemoteFragment == null) {
navigationRemoteFragment = new NavigationRemoteFragment();
navigationRemoteFragment.callbackHandler = handler;
}
return navigationRemoteFragment;
}
public NavigationRemoteFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_navigation_remote, container, false);
navBtnCenter = (ImageButton)v.findViewById(R.id.navBtnCenter);
navBtnCenter.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
callbackHandler.navigationRemoteSendCommand("/debug");
}
});
return v;
}
}
Callback interface
public interface NavigationRemoteCallbacks {
public void navigationRemoteSendCommand(String commandPath);
}
EDIT 1 code for MainGridPageAdapter
public class MainGridPageAdapter extends FragmentGridPagerAdapter {
private List<Fragment> fragmentList = null;
public MainGridPageAdapter(FragmentManager fm, List<Fragment> fragmentList) {
super(fm);
this.fragmentList = fragmentList;
}
#Override
public Fragment getFragment(int i, int i1) {
if (i1 < fragmentList.size()) return fragmentList.get(i1);
return null;
}
#Override
public int getRowCount() {
return 1;
}
#Override
public int getColumnCount(int i) {
return fragmentList.size();
}
You don't show the code for MainGridPageAdapter so I don't know how it is managing fragments. You mention that the problem occurs after a restart. Looking at the code in WearPlex.onCreate(), I suspect that the problem is caused fragments that are holding a reference to an old, destroyed instance of the activity.
A poorly documented behavior of FragmentManager is that it saves its state across restarts. This is often overlooked, resulting in duplicate fragment instances after a restart. The correct pattern for managing fragment creation in the onCreate() method of the host activity is:
if (savedInstanceState == null) {
// Not a restart
// Create a new instance of the fragment
// Add it to the fragment manager
} else {
// Restart
// The fragment manager has saved and restored the fragment instances
// Use findFragmentById() to get the fragment if you need it
}
You are not using savedInstanceState in onCreate() to test for restart. Are you seeing more fragments than you expect after restart? If so, the original fragments are holding a reference to the old activity, which was stopped, and has a disconnected GoogleApiClient. If the NavBtn of one of those fragments is clicked, you will see the "not connected" toast.
Update
The problem is caused by the way you are creating new instances of NavigationRemoteFragment, specifically the use of static member navigationRemoteFragment. After a restart, when the activity is recreated, the code calls NavigationRemoteFragment.getInstance(this). getInstance() finds navigationRemoteFragment not null because it is static, and does not create a new fragment. The fragment returned is the old one, which holds a reference to the old activity, which has been stopped and has a disconnected GoogleApiClient.
This could be confirmed by using the isDestroyed method and adding a some debug logging:
#Override
public void navigationRemoteSendCommand(String commandPath) {
if (isDestroyed()) {
Log.w("TEST", "This is an old instance of the activity");
}
sendMessage(commandPath, null);
}
I need to get friends details of the friends selected by the user.
The user can selected his friends using default facebook friend picker. But when I get the selection as a list of GraphUser I cannot see any details of the users. In particular, I cannot get the usernames.
Here is the Activity of the friend picker:
public class FacebookFriendPickerActivity extends FragmentActivity {
public static final String IEXTRA_SELECTED_FRIENDS = "selected friends";
private FriendPickerFragment friendPickerFragment;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_facebook_frind_picker);
Bundle args = getIntent().getExtras();
FragmentManager manager = getSupportFragmentManager();
Fragment fragmentToShow = null;
if (savedInstanceState == null) {
friendPickerFragment = new FriendPickerFragment(args);
} else {
friendPickerFragment = (FriendPickerFragment) manager.findFragmentById(R.id.picker_fragment);
}
// Set the listener to handle errors
friendPickerFragment.setOnErrorListener(new PickerFragment.OnErrorListener() {
#Override
public void onError(PickerFragment<?> fragment, FacebookException error) {
Toast.makeText(FacebookFriendPickerActivity.this, error.getLocalizedMessage(), Toast.LENGTH_LONG).show();
setResult(RESULT_CANCELED);
finish();
}
});
// Set the listener to handle button clicks
friendPickerFragment.setOnDoneButtonClickedListener(new PickerFragment.OnDoneButtonClickedListener() {
#Override
public void onDoneButtonClicked(PickerFragment<?> fragment) {
List<GraphUser> users = friendPickerFragment.getSelection();
if (users.size() > 0) {
ArrayList<String> usernames = new ArrayList<String>(users.size());
for (GraphUser user : users)
usernames.add(user.getUsername());
Intent data = new Intent();
data.putStringArrayListExtra(IEXTRA_SELECTED_FRIENDS, usernames);
setResult(RESULT_OK, data);
finish();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
});
fragmentToShow = friendPickerFragment;
manager.beginTransaction().replace(R.id.picker_fragment, fragmentToShow).commit();
}
#Override
protected void onStart() {
super.onStart();
friendPickerFragment.loadData(false);
}
}
When I try to read the list of usernames, I always get null.
Thanks in advance.
When you query for friend list with basic_info permission (which is default permission), you only get id and name fields. In order to get username field, you have to add it as an extra field to your friendPickerFragment.
It can simply be done by adding this line of code
friendPickerFragment.setExtraFields(Arrays.asList("username"));
Check setExtraFields method on related document here
I am in the process of creating an AuthenticationActivity which will provide users the option of logging in via Facebook, Twitter or app specific auth. After following the well written tutorial Use Facebook Login from the Facebook Android SDK documentation I had Facebook authentication working.
Unfortunately, when I proceeded to add a Twitter Fragment to provide similar login functionality I ran into issues. Suddenly the Facebook login button would not change state and my authentication to Facebook would not fully complete, though I could see authentication callbacks being returned.
After going round and round I finally decided to change the order that the AuthenticationActivity adds fragments via the SupportFragmentManager. When I did Facebook started working again, but then Twitter fragment broke.
getSupportFragmentManager()
.beginTransaction()
.add(android.R.id.content, twitterFragment)
.add(android.R.id.content, facebookFragment)
.commit();
Rather than make me feel better this made me feel worse. I admit to not fully understanding fragments, but am totally puzzled as to why the order in which the fragments are added has this affect. Which ever comes first does not work, but the one added second does. It may be important to note that I am utilizing actionbarsherlock and its fragment implementation.
Below you will find my activity and fragment code
AuthenticationActivity
public class AuthenticationActivity extends SherlockFragmentActivity {
private FacebookFragment facebookFragment;
private TwitterFragment twitterFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
addFragments();
} else {
restoreFragments();
}
}
private void addFragments() {
facebookFragment = new FacebookFragment();
twitterFragment = new TwitterFragment();
// TODO the order of this MATTERS if Facebook isn't last
// Facebook auth breaks and vice versa.
getSupportFragmentManager()
.beginTransaction()
.add(android.R.id.content, twitterFragment)
.add(android.R.id.content, facebookFragment)
.commit();
}
private void restoreFragments() {
facebookFragment = (FacebookFragment) getSupportFragmentManager()
.findFragmentById(android.R.id.content);
twitterFragment = (TwitterFragment) getSupportFragmentManager()
.findFragmentById(android.R.id.content);
}
}
Facebook Fragment (largely based on SDK documentation example)
public class FacebookFragment extends SherlockFragment {
private UiLifecycleHelper uiHelper;
private static final String TAG = FacebookFragment.class.getSimpleName();
private Session.StatusCallback callback = new Session.StatusCallback() {
#Override
public void call(Session session, SessionState state,
Exception exception) {
onSessionStateChange(session, state, exception);
}
};
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.authentication, container, false);
LoginButton facebookButton
= (LoginButton) view.findViewById(R.id.facebookButton);
facebookButton.setFragment(this);
return view;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
uiHelper = new UiLifecycleHelper(getActivity(), callback);
uiHelper.onCreate(savedInstanceState);
}
#Override
public void onResume() {
super.onResume();
Session session = Session.getActiveSession();
if (session != null && (session.isOpened() || session.isClosed())) {
onSessionStateChange(session, session.getState(), null);
}
uiHelper.onResume();
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
uiHelper.onActivityResult(requestCode, resultCode, data);
}
#Override
public void onPause() {
super.onPause();
uiHelper.onPause();
}
#Override
public void onDestroy() {
super.onDestroy();
uiHelper.onDestroy();
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
uiHelper.onSaveInstanceState(outState);
}
private void onSessionStateChange(Session session, SessionState state,
Exception exception) {
if (state.isOpened()) {
Log.i(TAG, "Logged in...");
} else if (state.isClosed()) {
Log.i(TAG, "Logged out...");
} else {
Log.i(TAG, "unknown state " + state);
}
}
}
TwitterFragment
public class TwitterFragment extends SherlockFragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.authentication, container, false);
Button twitterButton = (Button) view.findViewById(R.id.twitterButton);
twitterButton.setOnClickListener(twitterClickListener);
return view;
}
private OnClickListener twitterClickListener = new OnClickListener() {
public void onClick(View v) {
Context context = v.getContext();
Intent intent = new Intent(context, TwitterLoginActivity.class);
context.startActivity(intent);
}
};
}
Why does the order in which the above fragments are added via the SupportFragmentManger matter?
private void restoreFragments() {
facebookFragment = (FacebookFragment) getSupportFragmentManager()
.findFragmentById(android.R.id.content);
twitterFragment = (TwitterFragment) getSupportFragmentManager()
.findFragmentById(android.R.id.content);
}
You need to assign each fragment a different id otherwise you will always get the same fragment for a given id. I guess you'd get the first declared/added fragment but that depends in the implementation. (You probably are getting a class cast exception).
To fix this, assign them different ids and add them to different placeholders.
.beginTransaction()
.add(R.id.twitter_fragment, twitterFragment)
.add(R.id.facebook_fragment, facebookFragment)
.commit();
This requires other changes as well but hopefully you can get there.