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