Wednesday 30 December 2015

Understanding Android Activity and Fragment Lifecycle

Android activity lifecycle is pretty simple to understand if you understand the different states an activity goes through when it starts and when it tears down.

An activity has six states:
  1. Created
  2. Started
  3. Resumed
  4. Paused
  5. Stopped
  6. Destroyed


And there are seven callback methods used by the activity to move between different states:

  • onCreate() : This is the first callback android activity receives, when you start an activity. Here, you build and wire up your UI, initialize your global variables etc. Once that's done, your activity has been created.
  • onStart() :  This is called when the activity is becoming visible to the user.
  • onResume() : This is called when the activity will start interacting with the user. Here, at this point, the activity is in the foreground and the user can interact with it. The resumed state is sometimes referred to as the "running" state.
  • onPause() : This callback starts when the current activity is partially obscured by another activity or a dialog. This is typically used to commit unsaved changes to persistent data, stop animations, remove runnables and other things that may be consuming CPU, etc. The paused activity does not receive user input and cannot execute any code.
  • onStop() : This is called when the activity is no longer visible to the user and goes in the background, because another activity has been resumed and is covering this one. This may happen either because a new activity is being started, an existing one is being brought in front of this one, or this one is being destroyed. While stopped, the activity instance and all its state information such as member variables is retained, but it cannot execute any code.
  • onDestroy() : This is the final callback android activity receives, before it is destroyed. It can happen either because activity is finishing(someone called finish() on it) or because the system is temporarily destroying this instance of the activity to save space. Most apps don't need to implement this method because local class references are destroyed with the activity and your activity should perform most cleanup during onPause() and onStop(). However, if your activity includes background threads that you created during onCreate() or other long-running resources that could potentially leak memory if not properly closed, you should kill them during onDestroy(). 
         So, that's sum up the basic activity lifecycle.

   Callbacks that are executed in following scenarios:
    1)When Activity starts and becomes visible:
        onCreate()->onStart()->onResume()
    
    2)When phone/device rotates:
        onPause()->onStop()->onDestroy()->onCreate()->onStart()->onResume()
     
    3)When you press home button (activity goes in background, becomes invisible):
        onPause()->onStop()

    4)When you choose your app from recent apps(your activity comes again to foreground):
        onRestart()->onStart()->onResume()

    5)When you press the back button on your activity:
       onBackPressed()->finish()->onPause()->onStop()->onDestroy()

    6)When you add a fragment to your activity using add():
        Fragment fragmentA = FragmentA.newInstance("hello", "frag A");
    FragmentManager fragmentManager = getSupportFragmentManager();
    fragmentManager.beginTransaction()
        .add(R.id.container, fragmentA)
        .addToBackStack(null)
        .commit();
    
    Callbacks:
      FragmentA: newInstance()
      FragmentA: FragmentA()
      FragmentA: onAttach()
      FragmentA: onCreate()
      FragmentA: onCreateView()
      FragmentA: onViewCreated()
      FragmentA: onActivityCreated()
      FragmentA: onStart()
      FragmentA: onResume()

   What addToBackStack(null/String) means:--

  • It means adding this transaction(operation : add fragment A to the container) to the Back Stack.This means that the transaction will be remembered after it is committed(as added in back stack), and will reverse its operation when later popped off the stack.
  • If you want to navigate to the previous fragment add it to backstack. 

    7)When you press home button on a fragment that is added to an activity :
           MainActivity: onPause()
           FragmentA: onPause()
           MainActivity: onStop()
           FragmentA: onStop()


    8)When you choose your app from recent apps(your fragment comes again to foreground):
           MainActivity: onRestart()
           MainActivity: onStart()
           FragmentA: onStart()
           MainActivity: onResume()
           FragmentA: onResume()

    9)When you press back button on your fragment:
         MainActivity: onBackPressed()
         FragmentA: onPause()
         FragmentA: onStop()
         FragmentA: onDestroy()
         FragmentA: onDetach()

   Back button pressed on FragmentA:--


  • And so when you press back button on the FragmentA, then MainActivity onBackPressed() will call fm.popBackStack() and the top back stack entry(transaction) will be popped from the stack. 
  • And when the transaction will be popped out from the stack, its operation will be reversed(operation : remove fragment A from the container). And so, the fragment will be removed and destroyed.


   10)When you remove a fragment from an activity using remove():
         FragmentManager fm = getSupportFragmentManager();
     Fragment fragA = fm.findFragmentById(R.id.container);

     if (fragA == null) {
        return;
     }
     // Using popBackStack function to remove fragment A from main activity     
     // if (fm.getBackStackEntryCount() > 0) {     
     //      fm.popBackStack();     
     // }     
     if (fragA instanceof FragmentA) {
         fm.beginTransaction().remove(fragA).commit();
     }

    Callbacks:--
     FragmentA: onPause()
     FragmentA: onStop()

  
  11)Now, suppose we have another fragment B to be added in that same container where fragment A has been added in the MainActivity's container.


 case R.id.btn_add_fragment_A:

   FragmentA fragmentA = FragmentA.newInstance("hello", "frag A");

   Log.d(TAG, "getting fragmentManager inside btn_add_fragment_A click");
   fragmentManager = getSupportFragmentManager();
   fragmentManager.beginTransaction()
           .replace(R.id.container, fragmentA)
           .addToBackStack(null)
           .commit();

   break;

 Callbacks:-
 FragmentA: newInstance()
 FragmentA: FragmentA()
 MainActivity: getting fragmentManager inside btn_add_fragment_A click
 FragmentA: onAttach()
 FragmentA: onCreate()
 FragmentA: onCreateView()
 FragmentA: onViewCreated()
 FragmentA: onActivityCreated()
 FragmentA: onStart()
 FragmentA: onResume()

 case R.id.btn_add_fragment_B:
   fragmentB = FragmentB.newInstance("hello", "frag B");
   Log.d(TAG, "getting fragmentManager inside btn_add_fragment_B click");
   fragmentManager = getSupportFragmentManager();
   fragmentManager.beginTransaction()
       .replace(R.id.container, fragmentB)
       .addToBackStack(null)
       .commit();
   break;

 Callbacks:-
 FragmentB: newInstance()
 FragmentB: FragmentB()
 MainActivity: getting fragmentManager inside btn_add_fragment_B click
 FragmentA: onPause()
 FragmentA: onStop()
 FragmentB: onAttach()
 FragmentB: onCreate()
 FragmentB: onCreateView()
 FragmentB: onViewCreated()
 FragmentB: onActivityCreated()
 FragmentB: onStart()
 FragmentB: onResume()

 Note : When fragment B is added, FragmentAs onPause() and onStop() is  called but not onDetach() & onDestroy() because fragment A’s view is just removed from container and fragment A is not getting destroyed like it does on back press. Fragment B’s view is added to the container now.

 case R.id.btn_remove_fragment_B:
   Log.d(TAG, "getting fragmentManager inside btn_remove_fragment_B click");
   fragmentManager = getSupportFragmentManager();
   fragmentManager.popBackStack();
   break;

   Added fragment A =>  .replace(R.id.container, fragmentA) => addToBackStack(null)
   Added fragment B =>  .replace(R.id.container, fragmentB) => addToBackStack(null)
   Remove fragment B => fragmentManager.popBackStack();

 MainActivity: getting fragmentManager inside btn_remove_fragment_B click
 FragmentB: onPause()
 FragmentB: onStop()
 FragmentB: onDestroy()
 FragmentB: onDetach()
 FragmentA: onCreateView()
 FragmentA: onViewCreated()
 FragmentA: onActivityCreated()
 FragmentA: onStart()
 FragmentA: onResume()

 Explanation: Why while removing and destroying fragment B using popBackStack(), fragment A view was created again and resumed?
Ans: When you added fragment B, you used replace() and addToBackStack(), so all the  fragments were removed from the container, and fragment B was added to the container. And, also this transaction was recorded in the Back stack.  So, when fm.popBackStack() is called after fragment B has been added, first the transaction is popped out from the back stack and so the operations reverted itself, therefore fragment b is removed from the container and destroyed and all other fragments are added, for our case fragment A's view is added to the container. Also noted that fragment A's onAttach & onCreate() is not called because it has already been created & attached to the MainActivity earlier.



  References:
  http://developer.android.com/training/basics/activity-lifecycle/starting.html
  http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle
  http://developer.android.com/training/basics/activity-lifecycle/stopping.html




Saturday 13 June 2015

Sending an image from an android app to a webserver


I thought to start with this topic because when I first started programming for android, this problem took a lot of time, so I thought to share this piece with all of you.

I have used httpmime-4.1 jar  in the android application to enable sending of the images.

It requires just three steps.

First, create a new android application and paste the below code in your activity_main.xml.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="wrap_content"
        android:layout_height="350dp"
        android:scaleType="centerCrop" />

    <ProgressBar
        android:id="@+id/pb_load"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

    <Button
        android:id="@+id/btn_upload"
        android:layout_width="180dp"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="8dp"
        android:text="@string/upload"
        android:textSize="20sp" />

</LinearLayout>
Second, use the below code in your MainActivity.java


 public class MainActivity extends Activity {  
      private Bitmap bitmap;  
      private ImageView imageView;  
      private Button uploadButton;  
      private ProgressBar spinner;  
      @Override  
      protected void onCreate(Bundle savedInstanceState) {  
           super.onCreate(savedInstanceState);  
           setContentView(R.layout.activity_main);  
           spinner = (ProgressBar) findViewById(R.id.pb_load);  
           spinner.setVisibility(View.GONE);  
           InputStream is = getResources().openRawResource(R.drawable.cat);  
           bitmap = BitmapFactory.decodeStream(is); // This gets the image  
           // bitmap = BitmapFactory  
           // .decodeFile("/storage/emulated/0/DCIM/Camera/IMG_02.jpg");  
           imageView = (ImageView) findViewById(R.id.iv_image);  
           imageView.setImageBitmap(bitmap);  
           uploadButton = (Button) findViewById(R.id.btn_upload);  
           // CompressFormat set up to JPG, you can change to PNG or whatever you  
           // want;  
           // To send compressed image through the network,Write a compressed  
           // version of the  
           // bitmap to the specified ByteArray OutputStream.  
           ByteArrayOutputStream bos = new ByteArrayOutputStream();  
           bitmap.compress(Bitmap.CompressFormat.JPEG, 60, bos);  
           final byte[] data = bos.toByteArray();  
           uploadButton.setOnClickListener(new View.OnClickListener() {  
                @Override  
                public void onClick(View v) {  
                     // Create a media file name  
                     String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss",  
                     Locale.ENGLISH).format(new Date());  
                     // Creates a new ByteArrayBody with byte array content and file  
                     // name  
                     ByteArrayBody bdb = new ByteArrayBody(data, "temp_" + timeStamp + ".jpg");  
                     new SendImageTask().execute(bdb);  
                }  
           });  
      }  
      private class SendImageTask extends AsyncTask < ByteArrayBody, Void, String > {  
           @Override  
           protected void onPreExecute() {  
                // To show the progress circle while uploading image  
                spinner.setVisibility(View.VISIBLE);  
                uploadButton.setVisibility(View.GONE);  
           }  
           protected String doInBackground(ByteArrayBody...params) {  
                String responseBody;  
                try {  
                     HttpClient httpClient = new DefaultHttpClient();  
                     HttpContext localContext = new BasicHttpContext();  
                     // here, change it to your api ;  
                     HttpPost httpPost = new HttpPost(  
                          "http://192.168.1.114:8080/upload");  
                     MultipartEntity entity = new MultipartEntity(  
                     HttpMultipartMode.BROWSER_COMPATIBLE);  
                     // sending a String param;  
                     entity.addPart("myParam", new StringBody("myValue"));  
                     // sending an Image;  
                     entity.addPart("myImage", params[0]);  
                     httpPost.setEntity(entity);  
                     HttpResponse response = httpClient.execute(httpPost,  
                     localContext);  
                     int httpStatusCode = response.getStatusLine().getStatusCode();  
                     Log.d(" http status: ", "" + httpStatusCode);  
                     if (HttpStatus.SC_OK == httpStatusCode) {  
                          responseBody = EntityUtils.toString(response.getEntity());  
                          return responseBody;  
                     } else {  
                          Log.d("unhandled httpStatusCode: ", "" + httpStatusCode);  
                          // Closes the connection.  
                          // Returns a content stream of the entity.  
                          response.getEntity().getContent().close();  
                          throw new IOException(response.getStatusLine()  
                               .getReasonPhrase());  
                     }  
                } catch (ClientProtocolException e) {  
                     return e.getMessage().toString();  
                } catch (IOException e) {  
                     return e.getMessage().toString();  
                }  
           }  
           protected void onPostExecute(String result) {  
                spinner.setVisibility(View.GONE);  
                uploadButton.setVisibility(View.VISIBLE);  
                Toast.makeText(getApplicationContext(), "Output: " + result,  
                Toast.LENGTH_SHORT).show();  
           }  
      }  
 }  

Your android application is ready to send image.

Now, in step three, write the script to run in your server or localhost to accept and save the image and expose that API to your android application.

Below is a python script using bottle framework :


from bottle import get, post, request,run,os # or route

def get_save_path_for_category():
    return "/home/uploadhere"

@get('/upload') #
def login():
    return '''
        <form action="/upload" method="post" enctype="multipart/form-data">
            Category:      <input type="text" name="category" />
            Select a file: <input type="file" name="myImage" />
            <input type="submit" value="Start upload" />
        </form>
    '''

@post('/upload')  # or @route('/upload', method='POST')
def do_upload():
    #category   = request.forms.get('category')
    upload     = request.files.get('myImage')
    name, ext = os.path.splitext(upload.filename)
    if ext not in ('.png','.jpg','.jpeg'):
        return 'File extension not allowed.'

    save_path = get_save_path_for_category()
    if not os.path.exists(save_path):
        os.makedirs(save_path)
    upload.save(save_path,overwrite=True) # appends upload.filename automatically
    return "File successfully saved to '{0}'.".format(save_path)

run(host='192.168.1.114', port=8080, debug=True)

You are done here.