Home Proper way of implementing login with fragments
Reply: 2

Proper way of implementing login with fragments

Benjamin Lesaffre
1#
Benjamin Lesaffre Published in 2018-01-13 00:13:00Z

I am currently developing an app that uses Facebook and Google login. Until now, I used a LoginActivity which starts MainActivity when login is successful.

I want to rewrite it using fragments instead of two activities, so what I want to do is, when MainActivity starts, if user is not logged in, display LoginFragment, which does exactly what my LoginActivity did, but what I am missing is how to properly exit LoginFragment, and go back to my main fragment, which is DashboardFragment.

I have read a lot of threads about that but there are so many ways of doing it, that I can't figure out which is the best. Furthermore, solutions given were often like getActivity.onBackPressed() which I don't think is a proper way of doing what I want.

Here is what I do in the onCreate method of my MainActivity

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    BottomNavigationView navigation = findViewById(R.id.navigation);
    navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);

    FragmentManager fragmentManager = getFragmentManager();

    navigation.setVisibility(View.INVISIBLE);
    fragmentManager.beginTransaction()
            .replace(R.id.contentLogin, new LoginFragment()).commit();
}

I replace LoginFragment in contentLogin because I have another FrameLayout content which is above my BottomNavigationView, I want the LoginFragment to take all the available place.

I also override onActivityResult

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    Fragment fragment = getFragmentManager().findFragmentById(R.id.contentLogin);
    fragment.onActivityResult(requestCode, resultCode, data);
}

And in my LoginFragment

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.fragment_login, container, false);

    callbackManager = CallbackManager.Factory.create();

    LoginManager.getInstance().registerCallback(callbackManager,
            new FacebookCallback<LoginResult>() {
                @Override
                public void onSuccess(LoginResult loginResult) {
                    final String token = loginResult.getAccessToken().getToken();
                    Toast.makeText(getActivity(), token, Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onCancel() {
                    Toast.makeText(getActivity(), "Login canceled", Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onError(FacebookException exception) {
                    Toast.makeText(getActivity(), "Login error", Toast.LENGTH_SHORT).show();
                }
            });

    return view;
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    callbackManager.onActivityResult(requestCode, resultCode, data);
    super.onActivityResult(requestCode, resultCode, data);
}

Only the Facebook part to keep it simple.

Update 14.01

I got it to work with the following code :

MainActivity :

@Override
public void onBackPressed() {
    Fragment fragment = fragmentManager.findFragmentById(R.id.content);

    if (fragment instanceof LoginFragment) {
        fragmentManager.beginTransaction().replace(R.id.content, new DashboardFragment())
                .commit();
        navigation.setVisibility(View.VISIBLE);
    } else {
        super.onBackPressed();
    }
}

LoginFragment :

                @Override
                public void onSuccess(LoginResult loginResult) {
                    final String token = loginResult.getAccessToken().getToken();
                    final String user = loginResult.getAccessToken().getUserId();
                    loginStatus.setText("Login Success :\n" + user + "\n" + token);
                    getActivity().onBackPressed();
                }

There is still one major problem :

  • When I used two Activities, checking Facebook status worked perfectly, now what happens is that if the user is already login, the app will properly display DashboardFragment, but the Facebook login activity will appear above (like if I clicked the Facebook login button) and after logging in, the app will crash. I am using this code to check for login status :

    if (accessToken != null)
        LoginManager.getInstance().logInWithReadPermissions(this,
                Collections.singletonList("public_profile"));
    else {
        navigation.setVisibility(View.GONE);
        fragmentManager.beginTransaction().replace(R.id.content, new LoginFragment())
     .commit();
    }
    

and this is the exception I get :

E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: fr.pinty, PID: 24751
                  java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=64206, result=-1, data=Intent { (has extras) }} to activity {fr.pinty/fr.pinty.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.app.Fragment.onActivityResult(int, int, android.content.Intent)' on a null object reference
                      at android.app.ActivityThread.deliverResults(ActivityThread.java:4089)
                      at android.app.ActivityThread.handleSendResult(ActivityThread.java:4132)
                      at android.app.ActivityThread.-wrap20(ActivityThread.java)
                      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1533)
                      at android.os.Handler.dispatchMessage(Handler.java:102)
                      at android.os.Looper.loop(Looper.java:154)
                      at android.app.ActivityThread.main(ActivityThread.java:6119)
                      at java.lang.reflect.Method.invoke(Native Method)
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
                   Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.app.Fragment.onActivityResult(int, int, android.content.Intent)' on a null object reference
                      at fr.pinty.MainActivity.onActivityResult(MainActivity.java:92)
                      at android.app.Activity.dispatchActivityResult(Activity.java:6932)
                      at android.app.ActivityThread.deliverResults(ActivityThread.java:4085)
                      at android.app.ActivityThread.handleSendResult(ActivityThread.java:4132) 
                      at android.app.ActivityThread.-wrap20(ActivityThread.java) 
                      at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1533) 
                      at android.os.Handler.dispatchMessage(Handler.java:102) 
                      at android.os.Looper.loop(Looper.java:154) 
                      at android.app.ActivityThread.main(ActivityThread.java:6119) 
                      at java.lang.reflect.Method.invoke(Native Method) 
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) 
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

and as I only implemented Facebook login for now, I guess doing Google login will bring more problems. 

Developer_vaibhav
2#
Developer_vaibhav Reply to 2018-01-13 06:35:31Z

First Create MainActivity. In activity_main.xml file. Create a framelayout or you can use merge tag.

Declare its id for the frame layout . like @+id/fragment_container to your frame layout.

Then create your login fragment. Load your login fragment to your main activity's framelayout like this

        FragmentTransaction transaction=null;
        transaction=getFragmentManager().beginTransaction();
        transaction.replace(R.id.fragment_container,new LoginFragment());
        transaction.commit();

Then override onBackPressed in your MainActivity.java file inside onBackPressed method write this code and make sure to remove super.onBackPressed() method when you overrided onBackPressed

  FragmentManager manager=getSupportFragmentManager();
    Fragment current_frag= manager.findFragmentById(R.id.fragment_container);

  if(current_frag instanceof SignInFragment)
 {

     finish();
          }
Mordag
3#
Mordag Reply to 2018-01-14 19:38:26Z

Well I would say that using getActivity().onBackPressed is completly valid.

You could override it's implementation in your activity and check for the current count of fragments (inside the backstack). Of course you should maintain your backstack (like to removing the LoginFragment after moving to the DashboardFragment).

I would recommend not to use activity result to reduce the complexity. Why not starting the request >> onSucess, show next Fragment; else show error. If the user opens the app, the app should determine by the given values and session (valid/renewable?) if the LoginActivity needs to be shown.

I haven't used Facebook/Google login in any project, but I hope this general advise helps in finding a solution for your problem.

Update based on the comment: (14-01-2018)

Your onBackPressed implementation could look like that.

@Override
public void onBackPressed() {
   int count = getSupportFragmentManager().getBackStackEntryCount();
   if(count == 0) {
      finish();
   } else {
      super.onBackPressed();
   }
}

This part will close your app if the back stack for your app is empty.

Regarding the LoginFragment: The FragmentTransaction has different methods like addToBackStack. In general you should call that method if you want add the next fragment to your back stack. In your example you should avoid that for the LoginFragment.

LoginManager.getInstance().registerCallback(callbackManager,
            new FacebookCallback<LoginResult>() {
                @Override
                public void onSuccess(LoginResult loginResult) {
                    final String token = loginResult.getAccessToken().getToken();
                    Toast.makeText(getActivity(), token, Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onCancel() {
                    Toast.makeText(getActivity(), "Login canceled", Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onError(FacebookException exception) {
                    Toast.makeText(getActivity(), "Login error", Toast.LENGTH_SHORT).show();
                }
            });

That part doesn't look like that it has the need to use onActivityResult, are you sure about that? You can execute a new FragmentTransaction when the onSuccess is called.

You could start the next Fragment like that (keep in mind that you need to replace the R.id.fragment_container):

FragmentTransaction  transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, new DashboardFragment());
transaction.addToBackStack(null);
transaction.commit();
You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.304312 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO