Quantcast
Channel: Intel Developer Zone Articles
Viewing all articles
Browse latest Browse all 3384

Making Your Android* Application Login Ready Part II

$
0
0

Introduction

In part I we explored adding a login screen to our restaurant application and then customizing the rest of the application based on the access level of who logs in. That way, when the manager logs in, they can do their managerial tasks like editing the menu and analyzing restaurant sales, while the customer can see their coupons and reward points. Part I can be read here:

Making Your Android* Application Login Ready Part I

Now in part II we will cover sending and receiving calls to and from a server to handle the user login logic. That way the user will be able to log into any tablet in the restaurant or any other chain location. The users will be stored in a MongoDB* on the server side which can be accessed by their RESTful endpoints using the Spring* IO library. To learn more about the server component and how to set it up:

Accessing a REST Based Database Backend From an Android* App

Adding app-to-server communication adds another layer of complexity to our application. We need to add error handling for when the tablet has no internet connection and for when the server is offline in addition to HTTP errors.

Verify Internet Connection

Before the customer logins in and tries to connect to the server, we will want to verify that the device is connected. There is no point in trying to talk to a server, when the device itself isn’t even on the network. So before launching into the login screen, we check that the device has access to either Wi-Fi or cell connection. 

public Boolean wifiConnected(){
ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
	NetworkInfo mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
       	NetworkInfo mCellular = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
        	return (mWifi.isConnected() && mWifi.isAvailable()) || (mCellular.isConnected() && mCellular.isAvailable());
}

public void wifiNotConnected(){
	Intent intent = new Intent(LoginViewActivity.this, OrderViewDialogue.class);
	intent.putExtra(DIALOGUE_MESSAGE, getString(R.string.wifi_error));
	startActivity(intent);
	mUserFactory.logoutUser();
	mSignInButton.setEnabled(true);
	mRegisterButton.setEnabled(true);
	MainActivity.setCurrentUser(null);
	mSignInProgress= STATE_DEFAULT;
	LoginViewActivity.this.finish();
}

Code Example 1: Check data connection

We will do this in the onResume() method to ensure it is always checked before it starts the login activity. If wifi/data is connected, then we can launch the intent specific to the access level of the user who is logged in. 

Figure 1: Screenshot of the restaurant application’s manager portal

Async Task

To make the calls to the server, we don’t want to interfere with the rest of the application by using the main UI thread. Instead we will use an AsyncTask to asynchronously make the call in the background. This will be used to make the login call for the HTTP GET, the register call for HTTP PUT, the update call for HTTP POST, and the delete for HTTP DELETE.

To demonstrate how to use an AsyncTask, the following is how to set up the calls for the user login for the HTTP GET. When the user clicks login, we first retrieve the inputs and set up the AsyncTask as seen below. 

final String email=mUser.getText().toString();
                final String password=mPassword.getText().toString();
                new AsyncTask<String, Void, String>() {
		//… Async Methods
}.execute();

Code Example 2: Overview of AsyncTask for login call**

The Async methods we need are onPreExecute, doInBackground, onPostExecute, onCancelled. In the first method, we give the user feedback that the application is starting to log in by setting the status message and disabling the buttons from subsequent login attempts. We will also set up a Handler to cancel the task should the server take too long to respond, this will trigger the onCancelled method to be called.  

@Override
                    protected void onPreExecute() {
                        //set the state
                        mStatus.setText(R.string.status_signing_in);
                        mSignInProgress= STATE_IN_PROGRESS;
                        //disable subsequent log-in attempts
                        mSignInButton.setEnabled(false);
                        mRegisterButton.setEnabled(false);
                        //cancel the task if takes too long
                        final Handler handler = new Handler();
                        final Runnable r = new Runnable()
                        {
                            public void run()
                            {
                                cancel(true);
                            }
                        };
                        handler.postDelayed(r, 15000);
                    }

Code Example 3: AsyncTask onPreExecute() method **

The doInBackground is self-explanatory; this is where our method to communicate to the server is called which will all happen in a thread separate from the main UI thread. Hence the user is free to continue exploring and it won’t appear that the app has frozen. 

@Override
                    protected String doInBackground(String... params) {
                        String results="";
                        try {
                            mUserFactory.loginUserRestServer(email, password);
                        } catch (Exception e) {
                            results= e.getMessage();
                        }
                        return results;
                    }

Code Example 4: AsyncTask doInBackground() method**

Once the call to the server is complete and we get a response back, we move onto the onPostExecute method. Here we will handle displaying any errors to the user or informing them that they are now logged in. Note that setting the user variables in the code is done in the loginUserRestServer method that we called in the doInBackground(), you will see that explained later on in this article. 

@Override
                    protected void onPostExecute(String result) {
                        mSignInProgress= STATE_DEFAULT;
                        if((result!=null) && result.equals("")){
                            Intent intent = new Intent(LoginViewActivity.this, OrderViewDialogue.class);
                            intent.putExtra(DIALOGUE_MESSAGE, String.format(getString(R.string.signed_in_as),MainActivity.getCurrentUser().firstName));
                            startActivity(intent);
                        }else{
                            mStatus.setText(String.format(getString(R.string.status_sign_in_error),result));
                            mSignInButton.setEnabled(true);
                            mRegisterButton.setEnabled(true);
                        }
                    }

Code Example 5: AsyncTask onPostExecute() method**

Finally, in the onCancelled method, we will inform the user that there was error and enable the buttons again so the user can retry. 

@Override
                    protected void onCancelled(){
                        mStatus.setText("Error communicating with the server.");
                        mSignInButton.setEnabled(true);
                        mRegisterButton.setEnabled(true);
                    }

Code Example 6: AsyncTask onCancelled() method **

Server Calls

For the GET call our Spring IO server, we will search for the user’s login credentials in the database using a findByEmailAndPassword query method defined on the server side.  It will return a JSON response which will be parsed into a local user variable. Our handler also notifies our navigation drawer to update and display the user level specific options. If you examine the code below you will see that we send the password as is to the server, in the real world you should encrypt it with a PBKDF2 hash with salt at the very least. There are also various encryption libraries online or switch to an HTTPS capable server. We will also check for any input errors here, thus eliminating any delay by sending bad input to the server to evaluate. 

public void loginUserRestServer(String email, String password) throws Exception {
        if(email.length() == 0){
            throw new Exception("Please enter email.");
        }
        if(password.length()==0){
            throw new Exception("Please enter password.");
        }

        UserRestServer result = null;
        User user= new User();
        String url = "http://<server-ip>:8181/users/";
        RestTemplate rest = new RestTemplate();
        rest.getMessageConverters().add(new MappingJackson2HttpMessageConverter());

        try {
            String queryURL = url + "search/findByEmailAndPassword?name=" + email+"&password="+password;
            Users theUser = rest.getForObject(queryURL, Users.class);
            if (!(theUser.getEmbedded() == null)) {
                result = theUser.getEmbedded().getUser().get(0);
                user.setFirstName(result.getFirstName());
                user.setLastName(result.getLastName());
                user.setEmail(result.getEmail());
                user.setAccessLevel(result.getAccessLevel());
            } else {
                throw new Exception("No user found or password is incorrect");
            }
        }catch (Exception e) {
            if(e instanceof ResourceAccessException){
                throw new Exception("Connection to server failed");
            }else {
                throw new Exception(e.getMessage());
            }
        }
        MainActivity.setCurrentUser(user);
        Message input= new Message();
        mHandler.sendMessage(input);
    }

Code Example 6: Login/GET Call to Rest Based Database Backend server **

When there is a new user and they need to register, the application will send a POST call to our server to add them to the database. First we check that the email is not already taken by another user and then we create a new user object to add to the server. By default, we give the user customer access; an existing manager can then change their access later if needed through the application.  

    public void registerRestServer(String first, String last, String email, String password) throws Exception{
        if(first.length() == 0){
            throw new Exception("Please enter first name.");
        }
        if(last.length()==0){
            throw new Exception("Please enter last name.");
        }
        if(email.length()==0){
            throw new Exception("Please enter email.");
        }
        if(password.length()==0){
            throw new Exception("Please enter password.");
        }
        String url = "http://<server-ip>:8181/users/";
        RestTemplate rest = new RestTemplate();
        rest.getMessageConverters().add(new MappingJackson2HttpMessageConverter());

        try {
            String queryURL = url + "search/findByEmail?name=" + email;
            Users theUser = rest.getForObject(queryURL, Users.class);
            if (theUser.getEmbedded() == null) {
                UserRestServer myUser = new UserRestServer();
                myUser.setFirstName(first);
                myUser.setLastName(last);
                myUser.setEmail(email);
                myUser.setPassword(password);
                myUser.setAccessLevel(CUSTOMER_ACCESS);
                rest.postForObject(url,myUser,Users.class);
            } else {
                throw new Exception("User already exists");
            }
        }catch (Exception e) {
            if(e instanceof ResourceAccessException){
                throw new Exception("Connection to server failed");
            }else {
                throw new Exception(e.getMessage());
            }
        }
    }

Code Example 7: Register/POST Call to Rest Based Database Backend server**

For a manager updating a user’s access level, the PUT call requires the href of the user on the server. As our application doesn’t store any information on users besides the current user, we must do a GET call to server to find out the href first. 

public void updateUserAccessRestServer(String email, String accessLevel) throws Exception{
        if(email.length()==0){
            throw new Exception("Please enter email.");
        }
        if(accessLevel.length()==0){
            throw new Exception("Please enter accessLevel.");
        }

        String url = "http://<server-ip>:8181/users/";
        RestTemplate rest = new RestTemplate();
        rest.getMessageConverters().add(new MappingJackson2HttpMessageConverter());

        try {
            String queryURL = url + "search/findByEmail?name=" + email;
            Users theUser = rest.getForObject(queryURL, Users.class);
            if (!(theUser.getEmbedded() == null)) {
                theUser.getEmbedded().getUser().get(0).setAccessLevel(accessLevel);
                String urlStr = theUser.getEmbedded().getUser().get(0).getLinks().getSelf().getHref();
                rest.put(new URI(urlStr),theUser.getEmbedded().getUser().get(0));
            } else {
                throw new Exception("User doesn't exist");
            }
        }   catch (Exception e) {
            if(e instanceof ResourceAccessException){
                throw new Exception("Connection to server failed");
            }else {
                throw new Exception(e.getMessage());
            }
        }
    }

Code Example 8: Update/PUT Call to Rest Based Database Backend server**

And again for the remove call, we need the href to delete the user if we are the manager. If it is the customer removing their own account though, the app can just referenced the user’s data (except for the password which is not stored). 

public void removeUserRestServer(String email, String password, boolean manager) throws Exception{
        if(email.length()==0){
            throw new Exception("Please enter email.");
        }
        if(password.length()==0 && !manager){
            throw new Exception("Please enter password for security reasons.");
        }

        String url = "http://<server-ip>:8181/users/";
        RestTemplate rest = new RestTemplate();
        rest.getMessageConverters().add(new MappingJackson2HttpMessageConverter());

        try {
            String queryURL;
            String exception;
            String urlStr;
            if(manager) {
                queryURL = url + "search/findByEmail?name=" + email;
                exception= "User doesn't exist";
            }else{
                queryURL= url + "search/findByEmailAndPassword?name=" + email+"&password="+password;
                exception= "User doesn't exist or password is incorrect";
            }
            Users theUser = rest.getForObject(queryURL, Users.class);
            if (!(theUser.getEmbedded() == null)) {
                if(manager) {
                    urlStr = theUser.getEmbedded().getUser().get(0).getLinks().getSelf().getHref();
                }else{
                    urlStr = MainActivity.getCurrentUser().getHref();
                }
                rest.delete(new URI(urlStr));
            } else {

                throw new Exception(exception);
            }
        }   catch (Exception e) {
                if(e instanceof ResourceAccessException){
                    throw new Exception("Connection to server failed");
                }else {
                    throw new Exception(e.getMessage());
                }
            }
    }

Code Example 9: Remove/DELETE Call to Rest Based Database Backend server**

If you already have a regular HTTP server that you would like to use, below is some example code for the GET call. 

    public void loginUserHTTPServer(String email, String password) throws Exception {
        if(email.length() == 0){
            throw new Exception("Please enter email.");
        }
        if(password.length()==0){
            throw new Exception("Please enter password.");
        }

        User result = new User();
        DefaultHttpClient httpClient = new DefaultHttpClient();
        String url = "http://10.0.2.2:8080/user";

        HttpGet httpGet = new HttpGet(url);

        HttpParams params = new BasicHttpParams();
        params.setParameter("email", email);
        params.setParameter("password", password);
        httpGet.setParams(params);
        try {
            HttpResponse response = httpClient.execute(httpGet);

            String responseString = EntityUtils.toString(response.getEntity());
            if (response.getStatusLine().getStatusCode() != 200) {
                String error = response.getStatusLine().toString();
                throw new Exception(error);
            }
            JSONObject json= new JSONObject(responseString);
            result.setEmail(email);
            result.setFirstName(json.getString("firstName"));
            result.setLastName(json.getString("lastName"));
            result.setAccessLevel(json.getString("accessLevel"));
        } catch (IOException e) {
            throw new Exception(e.getMessage());
        }
        MainActivity.setCurrentUser(result);
        Message input= new Message();
        mHandler.sendMessage(input);
    }

Code Example 7: Login Call to a HTTP server**

Summary

This series of articles has covered how to add login capabilities to our restaurant application. We added a login screen for users and some special abilities for manager’s to manage the users and the menu. And now in part two the application can now talk to our server and login seamlessly across different tablets.

 

References

Making Your Android* Application Login Ready Part I

Accessing a REST Based Database Backend From an Android* App

Building Dynamic UI for Android* Devices

About the Author

Whitney Foster is a software engineer at Intel in the Software Solutions Group working on scale enabling projects for Android applications.

 

*Other names and brands may be claimed as the property of others.
**This sample source code is released under the Intel Sample Source Code License AgreementLike   SubscribeAdd new commentFlag as spam  .Flag as inappropriate  Flag as Outdated 

 


Viewing all articles
Browse latest Browse all 3384

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>