2

I am new to Android development and am using the Android Studio after a short sting with AppInventor.

I have read up on starting an activity and getting the result back as well as examining the examples.

On my MainActivity I have a Login button which starts up the LoginActivity. For the sake of example I am passing an "extra" from the calling activity (MainActivity) to the called activity (LoginActivity) as follows and upon return (it doesn't really login anywhere) it sets two extra parameters that are supposed to be passed back to the MainActivity:

My LoginActivity is a slightly modified version of the template created by the New Activity Wizard. Basically I defined the OUTPARAM static strings (name of extra parameters) and added the code to read the incoming parameter and set the outgoing parameters:

package com.example.testapp.ui;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;

/**
 * Activity which displays a login screen to the user, offering registration as
 * well.
 */
public class LoginActivity extends Activity {
    // DEGT : Activity parameters passed via intent.putExtra
    static public final String INPARAM_METHOD = "com.example.testapp.ui.loginMethod";
    static public final String OUTPARAM_USERID = "com.example.testapp.ui.userId";
    static public final String OUTPARAM_PASSW = "com.example.testapp.ui.password";

    /**
     * A dummy authentication store containing known user names and passwords.
     * TODO: remove after connecting to a real authentication system.
     */
    private static final String[] DUMMY_CREDENTIALS = new String[]{
            "foo@example.com:hello",
            "bar@example.com:world"
    };

    /**
     * The default email to populate the email field with.
     */
    public static final String EXTRA_EMAIL = "com.example.android.authenticatordemo.extra.EMAIL";

    /**
     * Keep track of the login task to ensure we can cancel it if requested.
     */
    private UserLoginTask mAuthTask = null;

    // Values for email and password at the time of the login attempt.
    private String mEmail;
    private String mPassword;

    // UI references.
    private EditText mEmailView;
    private EditText mPasswordView;
    private View mLoginFormView;
    private View mLoginStatusView;
    private TextView mLoginStatusMessageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_login);
        setupActionBar();

        // Set up the login form.
        mEmail = getIntent().getStringExtra(EXTRA_EMAIL);   // TODO modify
        mEmailView = (EditText) findViewById(R.id.email);
        mEmailView.setText(mEmail);

        mPasswordView = (EditText) findViewById(R.id.password);
        mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
                if (id == R.id.login || id == EditorInfo.IME_NULL) {
                    attemptLogin();
                    return true;
                }
                return false;
            }
        });

        mLoginFormView = findViewById(R.id.login_form);
        mLoginStatusView = findViewById(R.id.login_status);
        mLoginStatusMessageView = (TextView) findViewById(R.id.login_status_message);

        findViewById(R.id.sign_in_button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                attemptLogin();
            }
        });
    }

    /**
     * Set up the {@link android.app.ActionBar}, if the API is available.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void setupActionBar() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            // Show the Up button in the action bar.
            getActionBar().setDisplayHomeAsUpEnabled(true);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                // This ID represents the Home or Up button. In the case of this
                // activity, the Up button is shown. Use NavUtils to allow users
                // to navigate up one level in the application structure. For
                // more details, see the Navigation pattern on Android Design:
                //
                // http://developer.android.com/design/patterns/navigation.html#up-vs-back
                //
                // TODO: If Settings has multiple levels, Up should navigate up
                // that hierarchy.
                NavUtils.navigateUpFromSameTask(this);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.login, menu);
        return true;
    }

    /**
     * Attempts to sign in or register the account specified by the login form.
     * If there are form errors (invalid email, missing fields, etc.), the
     * errors are presented and no actual login attempt is made.
     */
    public void attemptLogin() {
        if (mAuthTask != null) {
            return;
        }

        // Reset errors.
        mEmailView.setError(null);
        mPasswordView.setError(null);

        // Store values at the time of the login attempt.
        mEmail = mEmailView.getText().toString();
        mPassword = mPasswordView.getText().toString();

        boolean cancel = false;
        View focusView = null;

        // Check for a valid password.
        if (TextUtils.isEmpty(mPassword)) {
            mPasswordView.setError(getString(R.string.error_field_required));
            focusView = mPasswordView;
            cancel = true;
        } else if (mPassword.length() < 4) {
            mPasswordView.setError(getString(R.string.error_invalid_password));
            focusView = mPasswordView;
            cancel = true;
        }

        // Check for a valid email address.
        if (TextUtils.isEmpty(mEmail)) {
            mEmailView.setError(getString(R.string.error_field_required));
            focusView = mEmailView;
            cancel = true;
        } else if (!mEmail.contains("@")) {
            mEmailView.setError(getString(R.string.error_invalid_email));
            focusView = mEmailView;
            cancel = true;
        }

        if (cancel) {
            // There was an error; don't attempt login and focus the first
            // form field with an error.
            focusView.requestFocus();
        } else {
            // Show a progress spinner, and kick off a background task to
            // perform the user login attempt.
            mLoginStatusMessageView.setText(R.string.login_progress_signing_in);
            showProgress(true);
            mAuthTask = new UserLoginTask();
            mAuthTask.execute((Void) null);
        }
    }

    /**
     * Shows the progress UI and hides the login form.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
    private void showProgress(final boolean show) {
        // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
        // for very easy animations. If available, use these APIs to fade-in
        // the progress spinner.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
            int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);

            mLoginStatusView.setVisibility(View.VISIBLE);
            mLoginStatusView.animate()
                    .setDuration(shortAnimTime)
                    .alpha(show ? 1 : 0)
                    .setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
                        }
                    });

            mLoginFormView.setVisibility(View.VISIBLE);
            mLoginFormView.animate()
                    .setDuration(shortAnimTime)
                    .alpha(show ? 0 : 1)
                    .setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
                        }
                    });
        } else {
            // The ViewPropertyAnimator APIs are not available, so simply show
            // and hide the relevant UI components.
            mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
            mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
        }
    }

    /**
     * Represents an asynchronous login/registration task used to authenticate
     * the user.
     */
    public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
        @Override
        protected Boolean doInBackground(Void... params) {
            // TODO: attempt authentication against a network service.

            try {
                // Simulate network access.
                Thread.sleep(2000);
                // TODO DEGT
                // 1. Use Server setting to select Login Service URL
                // 2. Download list of exhibition rooms to populate spinner on MainActivity


            } catch (InterruptedException e) {
                return false;
            }

            // get the intent extras (parameters passed to activity)
            android.content.Intent intent = getIntent();
            if (intent.getStringExtra(LoginActivity.INPARAM_METHOD).equalsIgnoreCase("manual"))
            {
                for (String credential : DUMMY_CREDENTIALS) {
                    String[] pieces = credential.split(":");
                    if (pieces[0].equals(mEmail)) {
                        // Account exists, return true if the password matches.
                        return pieces[1].equals(mPassword);
                    }
                }
            }


            // TODO: register the new account here.
            return true;
        }

        @Override
        protected void onPostExecute(final Boolean success) {
            mAuthTask = null;
            showProgress(false);

            // get the intent extras passed to the activity by calling activity
            android.content.Intent intent = getIntent();
            if (success) {
                // TO-TEST pass the credentials back to the calling activity
                intent.putExtra(OUTPARAM_USERID, mEmailView.getText());
                intent.putExtra(OUTPARAM_PASSW, mPasswordView.getText());
                setResult(RESULT_OK, intent);   // otherwise it returns RESULT_CANCELED

                    setIntent(intent);
                finish();
            } else {
                // TO-TEST pass the credentials back to the calling activity
                intent.putExtra(OUTPARAM_USERID, "");
                intent.putExtra(OUTPARAM_PASSW, "");
                    setIntent(intent);

                mPasswordView.setError(getString(R.string.error_incorrect_password));
                mPasswordView.requestFocus();
            }
        }

        @Override
        protected void onCancelled() {
            mAuthTask = null;
            showProgress(false);
        }
    }
}

In the UserLoginTask class (defined inside LoginActivity class) I check the input parameter (INPARAM_*) on the doInBackground() method. A debug breakpoint indicates I am getting the correct value set by the MainActivity.

On the onPostExecute() method I am setting both OUTPARAM extras. During debug I confirmed that these values are actually inserted in the Intent's Bundle. However, when control is passed back to the (calling) MainActivity, all the parameters (INPARAM, OUTPARAM) are ABSENT from the passed Intent in the data parameter of the onActivityResult method:

The (calling) MainActivity is as follows:

package com.example.testapp.ui;

import android.os.Bundle;
import android.app.Activity;
import android.provider.ContactsContract;
import android.view.Menu;
import android.widget.Button;   // Button
import android.view.View;       // View
import android.content.Intent;  // Intent
import android.widget.TextView;

public class MainActivity extends Activity {
    static public final int AUTHENTICATE_TICKET = 2;   // Login to server

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

        // Create the Login button onClick to start LoginActivity
        Button button= (Button) findViewById(R.id.buttonLogin);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                //startActivity(new Intent(MainActivity.this, LoginActivity.class));
                Intent intent = new Intent(MainActivity.this, LoginActivity.class);
                intent.putExtra(LoginActivity.INPARAM_METHOD, "manual"); // DEGT dummy for now
                startActivityForResult(intent, AUTHENTICATE_TICKET);

            }
        });
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);

        return true;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        // Check which request we're responding to
        if (requestCode == AUTHENTICATE_TICKET)
        {   // Successful LoginActivity result.
            if (resultCode == RESULT_OK)
            {
                // Get a reference to the TextView we will update
                final TextView txtMainMessage = (TextView)findViewById(R.id.textViewMainMessage);
                // Update the TextView with a personalized message. TODO Localize

                String userId = data.getExtras().getString(LoginActivity.OUTPARAM_USERID);
                String msg = "Hello " + userId + ". Now select the exhibition room please.";
                txtMainMessage.setText(msg);
            }
        }
    }
}
Lord of Scripts
  • 3,579
  • 5
  • 41
  • 62
  • Is `LoginActivity.OUTPARAM_USERID` and `OUTPARAM_USERID` one and the same?. Keys must match. Same for `PARAM_METHOD` and `MainActivity.PARAM_METHOD`. example `intent.putExtra("key", "manual")` and to get `intent.getStringExtra("key")`. – Raghunandan Jun 19 '13 at 22:37
  • Yes they are, the OUTPARAM values are defined as public static strings (the actual name of the parameter) in the LoginActivity class and used by both the calling activity and the invoked activity. – Lord of Scripts Jun 19 '13 at 22:42
  • post your complete code. you are using asynctask?. log your values and check – Raghunandan Jun 19 '13 at 22:44
  • As an aside, I would strongly suggest you do not use Android Studio at this time. It is a preview only and has many major bugs. – Michael Lawrie Jun 19 '13 at 22:45
  • Yes, it is an AsyncTask. – Lord of Scripts Jun 19 '13 at 22:51
  • `getIntent()` is a method of the activity. so why are you using this in asynctask? – Raghunandan Jun 19 '13 at 22:57
  • Posted complete code of both activities. The AsyncTask is defined INSIDE the activity class so it has access to the getIntent() method. Also, the async task is supposed to populate the OUTPARAM of the activity's Intent so that the calling activity (MainActivity) can read them. Am I missing something? – Lord of Scripts Jun 19 '13 at 23:06
  • seems your code is fine. cross checking again. – Raghunandan Jun 19 '13 at 23:29
  • @LordofScripts your keys don't match thats why its not working – Raghunandan Jun 19 '13 at 23:52
  • @Raghunandan as I said and you can see on the sources, the keys are defined in only one place (LoginActivity) as static strings and they are referenced from there. They keys are therefore the same. See onPostExecute (LoginActivity) and onActivityResult (MainActivity). I still get userId = null at onActivityResult. – Lord of Scripts Jun 20 '13 at 05:19
  • @LordofScripts makes the changes mentioned below and it should work. check the snap shot. i tried it before posting. Also check the reason. pls read the full post. you call finish in login activity onpostexecute. so your static variables might be garbage collected. your login activity is destroyed. so the keys won't match. don't use static strings for keys – Raghunandan Jun 20 '13 at 05:38

3 Answers3

3

The keys must match. Don't use static string as key. When your call finish() in onPostExecute() you activity is destroyed. So the static fields may be garbage collected. Hence won't match. I am not sure but a guess.

In your onPostExecute()

  Intent intent = getIntent();
  if (success) 
  {
      Toast.makeText(LoginActivity.this, mEmailView.getText(),1000).show();
      intent.putExtra("key",mEmailView.getText().toString());
      // intent.putExtra(OUTPARAM_PASSW, mPasswordView.getText());
      setResult(RESULT_OK, intent);   // otherwise it returns RESULT_CANCELED
      finish();
  }

In your onActivityResult

   final TextView txtMainMessage = (TextView)findViewById(R.id.textView1);
            // Update the TextView with a personalized message. TODO Localize

            String userId = data.getStringExtra("key");
            if(userId==null)
            {
                txtMainMessage.setText("Null");
            }else
            {
                    String msg = "Hello " + userId + ". Now select the exhibition room please.";
                 txtMainMessage.setText(msg);
            }

Snap shot of emulator

enter image description here

Raghunandan
  • 132,755
  • 26
  • 225
  • 256
  • Thanks I didn't notice this answer originally. One of the errors in my code was the lack of toString() in the getText(), it seemed superfluous but without it it doesn't work. – Lord of Scripts Jun 20 '13 at 06:48
  • The garbage collection issue seems to be the other problem though it seems strange to me (coming from C# world) that a static (classifier) member variable gets discarded when it is supposed to be valid across the execution of the application. I am not a fan of hard coded strings because these tend to become potential maintenance issues. In order to avoid that and the GC issue, I defined them as static final variables in a constants class (that cannot be instantiated) and that works fine. – Lord of Scripts Jun 20 '13 at 06:48
  • yes because when you call finish the LoginActivity is popped from the activity back stack and destroyed. So the static variables are available for garbage collection and will get garbage collected. When you return to the MainActivity the key's don't match. The hardcoded string will not affect performance in any way as it does not hold reference any where. – Raghunandan Jun 20 '13 at 06:51
  • 1
    Also check this http://developer.android.com/training/articles/perf-tips.html. scoll down and check the topic user static final over constants. in this case its fine to use harcoded string – Raghunandan Jun 20 '13 at 06:53
0

Create a new Intent to send data back through onActivityResult instead of using getIntent().

Intent intent = new Intent();
if (success) {
    // TO-TEST pass the credentials back to the calling activity
    intent.putExtra(OUTPARAM_USERID, mEmailView.getText());
    intent.putExtra(OUTPARAM_PASSW, mPasswordView.getText());
    setResult(RESULT_OK, intent);   // otherwise it returns RESULT_CANCELED

    finish();
} else {
    ...
}
Karakuri
  • 38,365
  • 12
  • 84
  • 104
  • Did that but the problem is the same, that is, the putExtra values are never conveyed back to the MainActivity's onActivityResult callback method. – Lord of Scripts Jun 19 '13 at 23:10
0

You have to use onNewIntent in your activity to read the passed parameters.

Whitebear
  • 1,761
  • 2
  • 12
  • 22