Wednesday, 10 February 2016

Setting a Custom Progress Bar using Linear Interpolator


Here we will be creating a custom progress bar with linear animation of progress.

Screenshot:



We need to create a progress bar in activity_main.xml layout file with a progressDrawable which sets the background color and progress color of ProgressBar widget.

In activity_main.xml

<ProgressBar
        android:id="@+id/progress_bar_retry"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="5dp"
        android:layout_centerInParent="true"
        android:indeterminate="false"
        android:progress="0"
        android:progressDrawable ="@drawable/progress_bar_retry_style" />


In progress_bar_retry_style.xml
<?xml version="1.0" encoding="utf-8"?>

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:id="@android:id/background">
        <shape>
            <solid android:color="@color/light_grey"/>
        </shape>
    </item>

    <item android:id="@android:id/progress">
        <clip>
            <shape>
                <solid android:color="@color/light_red"/>
            </shape>
        </clip>
    </item>
</layer-list>


Now, we have to set maximum value of progress:


Suppose, if you set max to 100 and your phone has 1400 pixels between 1 and 100 point.
Then when you set progress value 1 to 2, it will cover 14 pixels which will not be shown smooth to user.

So, that’s why we choose max to 500, so that when you cover progress 1 to progress 2 , it will cover 2.8 pixels(1400/500), which will be smooth for users.

private void setProgress() {

  if (mRetryHandler == null) {
    mRetryHandler = new Handler();
  }

  progress++;
  count++;
  

  mRetryProgressBar.setProgress(progress);

  if (progress >= MAX_PROGRESS) {
    Toast.makeText(getActivity(), "Your timer has finished", Toast.LENGTH_SHORT).show();
    return;
  } else {
    mRetryHandler.postDelayed(mRetryRunnable, 200);
  }
}


Basically above function will give animation, but it will feel very slow, because here we are updating progress value by 1,  5 times in 1 sec, i.e. by updating progress value to 5 in 1 sec.


We should not calculate intermediate progress values(by what progress values to be incremented in 1 sec). We should use LinerInterpolator to calculate intermediate values of progress to update in a sec.

Now, we will use ObjectAnimator to set LinearInterpolator to provide smooth animation by setting linearInterpolator on ObjectAnimator instance.

mRetryObjectAnimator.setInterpolator(mInterpolator);


static  ObjectAnimator
ofInt(Object target, String propertyName, int... values)
Constructs and returns an ObjectAnimator that animates between int values.

This subclass of ValueAnimator provides support for animating properties on target objects. The constructors of this class take parameters to define the target object that will be animated as well as the name of the property that will be animated. Appropriate set/get functions are then determined internally and the animation will call these functions as necessary to animate the property.

Parameters
targetThe object whose property is to be animated. This object should have a public method on it called setName(), where name is the value of the propertyName parameter.
propertyNameThe name of the property being animated.
valuesA set of values that the animation will animate between over time.



So, here is MainActivity.java code:



public class MainActivity extends AppCompatActivity {

    private static final int UPDATE_RETRY_PROGRESS_BAR = 1001;
    private static final int MAX_PROGRESS = 500;
    private Handler mRetryHandler;
    private ObjectAnimator mRetryObjectAnimator;
    private ProgressBar mRetryProgressBar;

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

        mRetryProgressBar = (ProgressBar) findViewById(R.id.progress_bar_retry);
        mRetryHandler = new RetryHandler();

        int timeFromServer = 60;
        Message msg = mRetryHandler.obtainMessage(UPDATE_RETRY_PROGRESS_BAR, timeFromServer, 0);
        mRetryHandler.sendMessage(msg);

    }

    private class RetryHandler extends Handler {

        private LinearInterpolator mInterpolator;

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case UPDATE_RETRY_PROGRESS_BAR:
                    int timeDuration = msg.arg1;
                    // for smooth progress bar update.
                    mRetryObjectAnimator =
                            ObjectAnimator.ofInt(mRetryProgressBar, "progress", MAX_PROGRESS);
                    mRetryObjectAnimator.setDuration(timeDuration * 1000);
                    if (mInterpolator == null) mInterpolator = new LinearInterpolator();
                    mRetryObjectAnimator.setInterpolator(mInterpolator);
                    mRetryObjectAnimator.start();

                    mRetryObjectAnimator.addListener(new Animator.AnimatorListener() {
                        @Override
                        public void onAnimationStart(Animator animation) {

                        }

                        @Override
                        public void onAnimationEnd(Animator animation) {
                            Toast.makeText(MainActivity.this, "Your timer has finished", Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void onAnimationCancel(Animator animation) {

                        }

                        @Override
                        public void onAnimationRepeat(Animator animation) {

                        }
                    });

                    break;
            }
        }
    }
}


Demo:



Reference:
http://developer.android.com/reference/android/animation/ObjectAnimator.html

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.