Saturday, March 7, 2015

107 MySuperHero Cloud Registration


.
107 MySuperHero Cloud Registration
In the previous tutorial, we have created a Settings page to store user details in local storage ie SharedPreferences.
In this tutorial, we are going to store the details in the Cloud so that later on the user would be able to perform online Backup and Restore

0) Starting Up

We need a live web site to handle our Cloud service. One of the free options available is www.hostinger.my .
Download the startup files and upload to your hostinger site (or you can also develop it offline first using WAMP package and later upload to hostinger or other hosting provider sites. I preferusbwebserver because it is small).
You need a REST Client Application e.g. Chrome POSTMAN Extension

1) REST Script File

The setup files consist of PHP Scripts and PHP Frameworks (SLIM and NOTORM). Slim is used for easy handling of REST APIs. NOTORM is used for easy interaction with databases.
Check that you have the following folders and files.
The index.php contains PHP Scripts based on SLIM Framework.
<?php
require 'Slim/Slim.php';
require 'NotORM.php';
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim();
//api-user
$app->post('/users','setUser');
$app->post('/users/:user_id','updateUser');
$app->post('/backup/:user_id','setData');
$app->post('/restore/:user_id','getData');
$app->run();
function setUser() {
        echo "setUser";
}
function updateUser($userid) {
        echo "updateUser for ".$userid;
}
function setData($userid) {
        echo "setData for ".$userid;
}
function getData($userid) {
        echo "getData for ".$userid;
}
?>
Run the REST Client Application (this tutorial uses Chrome Postman Apps) and send the following POST Method Request.

2) Database Setup

We are going to use SQLite database and PDO Class Connection.
The use of PDO Class Connection allows database to be changed later on so that we are not tied to any specific database during development.

2.1) Create Database

Click “Create new database”.
Enter database name “mydb”.

2.2) Create Table

Alternatively, run SQL commands below.
-- Adminer 4.2.4 SQLite 3 dump

DROP TABLE IF EXISTS "tbluserdata";
CREATE TABLE "tbluserdata" (
 "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
 "name" text NULL,
 "email" text NOT NULL,
 "photo" text NULL,
 "superheroes" text NULL
);

--

2.3) Test adding a dummy data.

The above form, when executed, will be translated into the following SQL
INSERT INTO "tbluserdata" ("name", "email", "photo", "superheroes")
VALUES ('boboiboy', 'boboiboy@gmail.com', NULL, NULL);

3) Data Entry Scripts

 3.1) Create database connection script

File: api/data_conn.php
<?php
error_reporting(E_ALL | E_STRICT);
$connection = new PDO("sqlite:../data/mydb.sdb");
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$connection->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
//~ $software->debug = true;
?>

3.2) Update API Script File with hardcoded data entries

Test with hardcoded data entries.
File: api/index.php
<?php
require 'Slim/Slim.php';
require 'NotORM.php';
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim();
$app->container->singleton('db', function () {
        include 'data_conn.php';
    return new NotORM($connection);
});
//api-user
$app->post('/users','setUser');
$app->post('/users/:user_id','updateUser');
$app->post('/backup/:user_id','setData');
$app->post('/restore/:user_id','getData');
$app->run();
function setUser() {
        echo "setUser";
        $app = \Slim\Slim::getInstance();
        $db1=$app->db;
        $userdata = $db1->tbluserdata();
        $data = array(
            "name" => "upin",
            "email" => "upin@gmail.com"
        );
        $result = $userdata->insert($data);
}
function updateUser($userid) {
        echo "updateUser for ".$userid;
}
function setData($userid) {
        echo "setData for ".$userid;
}
function getData($userid) {
        echo "getData for ".$userid;
}
?>
OUTCOME.
The SQL result for the above script
INSERT INTO "tbluserdata" ("name", "email", "photo", "superheroes")
VALUES ('upin', 'upin@gmail.com', NULL, NULL);

3.3) Update API Script with Post Parameter Inputs

Test with post parameters.
File: api/index.php
<?php
require 'Slim/Slim.php';
require 'NotORM.php';
\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim();
//$app->$db = new NotORM($connection);
$app->container->singleton('db', function () {
        include 'data_conn.php';
    return new NotORM($connection);
});
//api-user
$app->post('/users','setUser');
$app->post('/users/:user_id','updateUser');
$app->post('/backup/:user_id','setData');
$app->post('/restore/:user_id','getData');
$app->run();
function setUser() {
        //echo "setUser";
        $app = \Slim\Slim::getInstance();
        //create db1 object from app db object
        $db1=$app->db;
        //create userdata object as representation of
        //tbluserdata from db1 connection object
        $userdata = $db1->tbluserdata();
        //create response object as an array
        $response=array();
        $action="none";
        $actionstatus="none";
        $result="0";
        //create variables to store param values
        $name = $app->request->post('name');
        $email = $app->request->post('email');
        //if variables are not empty
        //then assign variable values to data array
        if (!(empty($name)) && !(empty($email)))  {
                //echo 'valid param';
                //find matching useremail to param email
                $registereduser = $db1->tbluserdata("email = ?", $email)->fetch();
                //echo $registereduser;
                //if matched(registered) then update record
                //else insert new record
                if (!empty($registereduser)) {//already registered
                        $action="update";
                    $data = array(
                                "name" => $name
                    );
                    $result = $registereduser->update($data);
                    $actionstatus="success";
                }
                else{
                        $action="insert";
                        $data = array(
                    "name" => $name,
                    "email" => $email
                        );
                        $result = $userdata->insert($data);        
                    $actionstatus="success";                
                }
        }
        //create app response
    $response = $app->response;
    //set response sontent type as json
    $response['Content-Type'] = 'application/json';
    //set response body
    //use json_encode to format the output
    $response->body( json_encode([
        'action' => $action,
        'actionstatus' => $actionstatus,
        'message' => $result
    ]));
}
function updateUser($userid) {
        echo "updateUser for ".$userid;
}
function setData($userid) {
        echo "setData for ".$userid;
}
function getData($userid) {
        echo "getData for ".$userid;
}
?>
OUTCOME.
DATA ENTRY TEST WITH POSTMAN.
DATA ENTRY RESULT TEST WITH ADMINER.
TRY RESUBMITTING IPIN RECORD AGAIN BUT WITH A DIFFERENT NAME.
We have completed a simple exercise to set up REST server using SLIM and NOTORM Framework.
We have created scripts to get post parameter and perform insert or update operation.
In the next tutorial, we will refine the scripts further.
 

DOWNLOAD


.

Sunday, March 1, 2015

106 MySuperHero User Settings


.
106 MySuperHero User Settings
Most of the apps have a page for users to keep some personal information such as profile name, email and photo.
We are going to create a Settings page and link this page to the Apps Menu
When the user presses the Menu button on Android Device, a pop up menu will appear and display the item Settings. Clicking the item will bring the user to the Settings page.

0) Starting Up

1) Create a new activity

Use Blank Activity template to create a new activity called SettingsActivity. (If you have forgotten the steps, refer http://android-steps.blogspot.my/2015/03/101-mysuperhero-android-splashscreen.html )

2) Link the options menu action to the page

File: SuperHeroesActivity.java
package com.example.mysuperhero;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class SuperHeroesActivity extends Activity {
 Button btnAddSuperHero;
 ListView lvwSuperHeroes;
 List < String > lstSuperHeroes = new ArrayList < String > (); //init lstSuperHeroes
 ArrayAdapter adpSuperHeroes;
 static final int ADD_ITEM = 1; // The request code to add item
 static final int EDIT_ITEM = 2; // The request code to edit item
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_super_heroes);
  setLvwSuperHeroes();
  setBtnAddSuperHero();
 }
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.activity_super_heroes, menu);
  return true;
 }
 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
     // Handle item selection
     switch (item.getItemId()) {
         case R.id.menu_settings:
             startActivity (new Intent(SuperHeroesActivity.this, SettingsActivity.class));
         default:
             return super.onOptionsItemSelected(item);
     }
 }
 private void setLvwSuperHeroes() {
  //bind object
  lvwSuperHeroes = (ListView) findViewById(R.id.lvw_superheroes);
  //load data from SharedPreferences to lstSuperHeroes if exists
  loadData();
  //define adpSuperHeroes having data source from lstSuperHeroes
  adpSuperHeroes = new ArrayAdapter < String > (this, android.R.layout.simple_list_item_1, lstSuperHeroes);
  //Plug adpSuperHeroes to lvwSuperHeroes
  lvwSuperHeroes.setAdapter(adpSuperHeroes);
  //set onclick listener for listview
  lvwSuperHeroes.setOnItemClickListener(new AdapterView.OnItemClickListener() {
   @Override
   public void onItemClick(AdapterView < ? > parent, View view, int position, long id) {
    Intent intent = new Intent(SuperHeroesActivity.this, SuperHeroActivity.class);
    intent.putExtra("POSITION", position);
    intent.putExtra("NAME", lvwSuperHeroes.getItemAtPosition(position).toString());
    startActivityForResult(intent, EDIT_ITEM);
   }
  });
  //set onItemLongClickListener
  lvwSuperHeroes.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
   public boolean onItemLongClick(AdapterView < ? > arg0, View v,
    int index, long arg3) {
    deleteSuperHero(index);
    //save changes (DELETE_ITEM) to SharedPreferences    
    saveData();
    return true;
   }
  });
 }
 private void setBtnAddSuperHero() {
  btnAddSuperHero = (Button) findViewById(R.id.btn_add_superhero);
  btnAddSuperHero.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View view) {
    Intent intent = new Intent(SuperHeroesActivity.this, SuperHeroActivity.class);
    startActivityForResult(intent, ADD_ITEM);
   }
  });
 }
 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  String strNewName = "";
  int intPosition = -1;
  if (resultCode == RESULT_OK) {
   if (requestCode == ADD_ITEM) {
    try {
     strNewName = data.getStringExtra("NAME");
    } catch (Exception e) {}
    addSuperHero(strNewName);
   }
   if (requestCode == EDIT_ITEM) {
    try {
     intPosition = data.getIntExtra("POSITION", -1);
     strNewName = data.getStringExtra("NAME");
    } catch (Exception e) {}
    editSuperHero(intPosition, strNewName);
   }
   //save changes (ADD_ITEM or EDIT_ITEM) to SharedPreferences
   saveData();
  } else if (resultCode == RESULT_CANCELED) {
   //Toast.makeText(getApplicationContext(), "No Changes", //Toast.LENGTH_SHORT).show();
  }
 }
 private void editSuperHero(int position, String text) {
  //check that we have a valid position value
  if (position < 0) {
   return;
  }
  //replace item in lstSuperHeroes
  lstSuperHeroes.set(position, text);
  //notify apps that adapter has changed
  adpSuperHeroes.notifyDataSetChanged();
  //scroll to the bottom
  lvwSuperHeroes.smoothScrollToPosition(adpSuperHeroes.getCount() - 1);
 }
 private void addSuperHero(String strNewName) {
  //add new item to lstSuperHeroes
  lstSuperHeroes.add(strNewName);
  //notify apps that adapter has changed
  adpSuperHeroes.notifyDataSetChanged();
  //scroll to the bottom
  lvwSuperHeroes.smoothScrollToPosition(adpSuperHeroes.getCount() - 1);
 }
 private void deleteSuperHero(int position) {
  //delete item in lstSuperHeroes
  lstSuperHeroes.remove(position);
  //notify apps that adapter has changed        
  adpSuperHeroes.notifyDataSetChanged();
 }
 private void loadData() {
  //get data from SharedPreferences Resource identified by appsdata
  SharedPreferences spAppsData = getSharedPreferences("appsdata", MODE_PRIVATE);
  //get comma-separated values from data item identified by superheroes (key)
  String csvSuperHeroes = spAppsData.getString("superheroes", "");
  if (!csvSuperHeroes.equals("")) {
   //convert comma-separated value to string array using split method
   //create new list array having values from string array
   lstSuperHeroes = new ArrayList < String > (Arrays.asList(csvSuperHeroes.split(",")));
  }
 }
 private void saveData() {
  //set data SharedPreferences Resource identified by appsdata
  SharedPreferences spAppsData = getSharedPreferences("appsdata", MODE_PRIVATE);
  //set edit mode
  SharedPreferences.Editor e = spAppsData.edit();
  //prepare comma-separated values from lstSuperHeroes
  String csvSuperHeroes = TextUtils.join(",", lstSuperHeroes);
  //put values into data item identified by superheroes (key)
  e.putString("superheroes", csvSuperHeroes);
  //make permanent
  e.commit();
 }
}
OUTCOME.

3) Create Layout

Save the image as res/drawable/user
File: res/layout/activity_settings.xml
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SettingsActivity" >
   <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/user"
        android:layout_marginTop="20dp"
       android:layout_gravity="center_horizontal"
       />
    <EditText
        android:id="@+id/etx_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:hint="Name" />
    <EditText
        android:id="@+id/etx_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:hint="Email" />
    <Button
        android:id="@+id/btn_register"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="Register"
        android:background="#e6e6e6" />
    <Button
        android:id="@+id/btn_backup"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="Backup To Cloud"
        android:background="#00bfff" />
    <Button
        android:id="@+id/btn_restore"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:text="Restore From Cloud"
        android:background="#ff00ff"  />        
</LinearLayout>
OUTCOME.

4) Save to SharedPreferences

File: SettingsActivity.java
package com.example.mysuperhero;
import java.util.ArrayList;
import java.util.Arrays;
import android.os.Bundle;
import android.app.Activity;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class SettingsActivity extends Activity {
 EditText etxName;
 EditText etxEmail;
 Button btnRegister, btnBackup, btnRestore;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_settings);
  setForm();
 }
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.activity_settings, menu);
  return true;
 }
 private void setForm() {
  etxName = (EditText) findViewById(R.id.etx_name);
  etxEmail = (EditText) findViewById(R.id.etx_email);
  loadData();
  btnRegister = (Button) findViewById(R.id.btn_register);
  btnRegister.setOnClickListener(new OnClickListener() {
   @Override
   public void onClick(View view) {
    if (isValidFormData()) {
     saveData();
     registerData();
    }
   }
   private boolean isValidFormData() {
    boolean isName = false, isEmail = false;
    String emailPattern = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+";
    String strTestValue = "";
    //get field values
    strTestValue = etxName.getText().toString();
    //check field values validity
    if (isEmptyString(strTestValue)) {
     etxName.setError("Name must not be empty");
     Toast.makeText(getApplicationContext(), "Name error", Toast.LENGTH_SHORT).show();
    } else {
     isName = true;
    }
    //get field values
    strTestValue = etxEmail.getText().toString();
    //check field values validity
    if (isEmptyString(strTestValue)) {
     etxEmail.setError("Email must not be empty");
     Toast.makeText(getApplicationContext(), "Email error", Toast.LENGTH_SHORT).show();
    } else {
     if (strTestValue.matches(emailPattern)) {
      isEmail = true;
     } else {
      etxEmail.setError("Enter valid Email format");
      Toast.makeText(getApplicationContext(), "Email error", Toast.LENGTH_SHORT).show();
     }
    }
    return (isName && isEmail);
   }
   // validating empty string
   public boolean isEmptyString(String text) {
    return (text == null || text.trim().equals("null") || text.trim()
     .length() <= 0);
   }
  });
 }
 private void registerData() {
  // TODO Auto-generated method stub
  Toast.makeText(getApplicationContext(), "Registering...", Toast.LENGTH_SHORT).show();
 }
 private void saveData() {
  //set data SharedPreferences Resource identified by userdata
  SharedPreferences spUserData = getSharedPreferences("userdata", MODE_PRIVATE);
  //set edit mode
  SharedPreferences.Editor e = spUserData.edit();
  //put values into data items
  e.putString("name", etxName.getText().toString());
  e.putString("email", etxEmail.getText().toString());
  //make permanent
  e.commit();
 }
 private void loadData() {
  //get data from SharedPreferences Resource identified by userdata
  SharedPreferences spUserData = getSharedPreferences("userdata", MODE_PRIVATE);
  //get values from data items
  etxName.setText(spUserData.getString("name", ""));
  etxEmail.setText(spUserData.getString("email", ""));
 }
}
Start.
Go to Settings
Enter details
Click Register
press Back Button to return to front page
Go to Settings again and you should see your last saved data
At this point, the apps only stores the data locally ie in Shared Preferences.
In the next tutorial, we shall make this apps registers for cloud service.

DOWNLOAD


.