Android - Update the UI in ViewPager Fragments Using Data from a Background Service

    In Android, Service is 1 of 4 basic components of an application (Activity, Intent, Service and ContentProvider). A service is a component that runs in the background to perform long-running operations without needing to interact with the user and it works even if application is destroyed. A service can essentially take two states: STARTED and BOUND.
    In many cases, you have to update Service data to UI so as that user can recognize Service status (i.e: download file process), so we must put data through Intent and use BroadcastReceiver to "listen" Service status and display it to UI. With this project, I hope that you can understand this data tranfer process and as well as have more experiences in Android application programming!

1. Project Description

- An Activity with a ViewPager in it.
- There are 3 Fragments  (pages) in ViewPager: DownLoadFragment, DateTimeFragment and a BlankFragment to "test".
- Includes 2 Services run in background: Download file Service (show it's status to DownloadFragment) and getting DateTime Service (show curent date/time to UI in DateTimeFragment).

2. Main Activity

    Firstly, create a main activity (named PagerActivity in this project), including a ViewPager and user PagerTabStrip to make tabs bar at top. Layout (activity_pager.xml) file:

    Set ViewPager adapter in programmatically code (PagerActivity.java):
package info.devexchanges.services.ui;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;

import info.devexchanges.services.R;
import info.devexchanges.services.adapter.ViewPagerAdapter;


public class PagerActivity extends FragmentActivity {

    private ViewPager pager;
    private ViewPagerAdapter adapter;

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

        pager = (ViewPager) findViewById(R.id.view_pager);

        //init view pager adapter
        adapter = new ViewPagerAdapter(getSupportFragmentManager());
        pager.setAdapter(adapter);
    }
}

3. Download Fragment and download file Service

    Now, we create 1st page of ViewPager by coding 2 file DownloadFragment and DownloadService. In this Fragment, we declared a ProgressBar and a TextView to update downloading process status, a Button to start download file when clicked. Layout for it (fragment_down_load.xml):
    For "listen" a Service status, provide a BroadcastReceiver object in Fragment and initialize it in onCreate() method. Therefore, create a new Intent with current Service:
 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                updateProgressBar(intent);
            }
        };
        intent = new Intent(getActivity(), DownloadService.class);
    }
    Register/unregister broadcast receiver in onStart()/onStop() of DownloadFragment LocalBroadcastManager instance:
@Override
    public void onStart() {
        super.onStart();

        if (isServiceRunning(DownloadService.class)) {
            btnDownload.setVisibility(View.GONE);
        }
        LocalBroadcastManager.getInstance(getActivity()).registerReceiver((broadcastReceiver),
                new IntentFilter(DownloadService.DOWNLOAD_ACTION)
        );
    }

    @Override
    public void onStop() {
        super.onStop();
        LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(broadcastReceiver);
    }
    Ofcourse, in this Fragment, we'll start download file after click Download Button:
/**
     * Handling button Download event
     * @return
     */
    private View.OnClickListener onStartDownloadListener() {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getActivity().startService(intent);
                btnDownload.setVisibility(View.GONE);

                //init BroadcastManager instance when service start
                LocalBroadcastManager.getInstance(getActivity()).registerReceiver((broadcastReceiver),
                        new IntentFilter(DownloadService.DOWNLOAD_ACTION)
                );
            }
        };
    }
     Another important method is updateProgressBar() to update percentage of download file process to ProgressBar and TextView:
 /**
     * Update the percentage of download process to TextView and Progress Bar
     * @param intent
     */
    private void updateProgressBar(Intent intent) {
        String progress = intent.getStringExtra(DownloadService.PERCENTAGE_STAMP);
        if (progress != null ) {
            progressBar.setProgress(Integer.parseInt(progress));
            percentage.setText(progress + "%");

            //stop download service when task completed
            if (progress.equals("100")) {
                getActivity().stopService(intent);
            }
        }
    }
    Adding some neccessary methods, we have full DownloadFragment.java code:
package info.devexchanges.services.ui;

import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.content.LocalBroadcastManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;

import info.devexchanges.services.DownloadService;
import info.devexchanges.services.R;

public class DownLoadFragment extends Fragment {

    private ProgressBar progressBar;
    private TextView percentage;
    private View btnDownload;
    private Intent intent;
    private BroadcastReceiver broadcastReceiver;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                updateProgressBar(intent);
            }
        };
        intent = new Intent(getActivity(), DownloadService.class);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_down_load, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        percentage = (TextView) view.findViewById(R.id.percent);
        progressBar = (ProgressBar) view.findViewById(R.id.progress_bar);
        btnDownload = (View) view.findViewById(R.id.btn_start);

        btnDownload.setOnClickListener(onStartDownloadListener());
    }

    @Override
    public void onStart() {
        super.onStart();

        if (isServiceRunning(DownloadService.class)) {
            btnDownload.setVisibility(View.GONE);
        }
        LocalBroadcastManager.getInstance(getActivity()).registerReceiver((broadcastReceiver),
                new IntentFilter(DownloadService.DOWNLOAD_ACTION)
        );
    }

    @Override
    public void onStop() {
        super.onStop();
        LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(broadcastReceiver);
    }

    /**
     * Update the percentage of download process to TextView and Progress Bar
     * @param intent
     */
    private void updateProgressBar(Intent intent) {
        String progress = intent.getStringExtra(DownloadService.PERCENTAGE_STAMP);
        if (progress != null ) {
            progressBar.setProgress(Integer.parseInt(progress));
            percentage.setText(progress + "%");

            //stop download service when task completed
            if (progress.equals("100")) {
                getActivity().stopService(intent);
            }
        }
    }

    /**
     * Handling button Download event
     * @return
     */
    private View.OnClickListener onStartDownloadListener() {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getActivity().startService(intent);
                btnDownload.setVisibility(View.GONE);

                //init BroadcastManager instance when service start
                LocalBroadcastManager.getInstance(getActivity()).registerReceiver((broadcastReceiver),
                        new IntentFilter(DownloadService.DOWNLOAD_ACTION)
                );
            }
        };
    }

    /**
     * Check if Service is running
     * @param serviceClass
     * @return true if one or more service is running in background thread
     */
    private boolean isServiceRunning(Class serviceClass) {
        ActivityManager manager = (ActivityManager) getActivity().getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
            if (serviceClass.getName().equals(service.service.getClassName())) {
                return true;
            }
        }
        return false;
    }
}

    In DownloadService.java, we provide an AsyncTask to download file trom URL, when it's running, in doInBackground(), calculating downloaded  percentages, and write data to file at Internal Storage. In onProgressUpdate(), sending this data to BroadcastReceiver:
/**
         * Updating progress bar
         */
        protected void onProgressUpdate(String... progress) {
            // setting progress percentage
            //put info through intent
            intent = new Intent(DOWNLOAD_ACTION);
            intent.putExtra(PERCENTAGE_STAMP, progress[0]);
            //sending intent through BroadcastManager
            broadcastManager.sendBroadcast(intent);
        }
And this is full code of AsyncTask:
    Creating BroadcastReceiver instance in onCreate() method of Service and start AsyncTask in onStartCommand():
 @Override
    public void onCreate() {
        super.onCreate();
        //init Intent
        broadcastManager = LocalBroadcastManager.getInstance(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        new DownloadFileFromURLTask().execute(DOWNLOAD_LINK);
        return START_STICKY;
    }
    Full code of DownloadService.java:
This screen when app running:

4. DateTime Fragment and get current date/time Service

     Difference with above Fragment, this one start Service when start:
 @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                updateDataToUI(intent);
            }
        };
        intent = new Intent(getActivity(), DateTimeService.class);
    }

    @Override
    public void onStart() {
        super.onStart();
        getActivity().startService(intent);
        LocalBroadcastManager.getInstance(getActivity()).registerReceiver((broadcastReceiver),
                new IntentFilter(DateTimeService.DATE_TIME_ACTION)
        );
    }

    @Override
    public void onStop() {
        super.onStop();
        getActivity().stopService(intent);
        LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(broadcastReceiver);
    }
    in updateDataToUI() method, we'll update Service data (current Date and Time) to TextViews. Full code of DateTimeFragment.java:
package info.devexchanges.services.ui;


import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.content.LocalBroadcastManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import info.devexchanges.services.DateTimeService;
import info.devexchanges.services.R;


public class DateTimeFragment extends Fragment {

    private TextView date;
    private TextView time;
    private Intent intent;
    private BroadcastReceiver broadcastReceiver;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                updateDataToUI(intent);
            }
        };
        intent = new Intent(getActivity(), DateTimeService.class);
    }

    @Override
    public void onStart() {
        super.onStart();
        getActivity().startService(intent);
        LocalBroadcastManager.getInstance(getActivity()).registerReceiver((broadcastReceiver),
                new IntentFilter(DateTimeService.DATE_TIME_ACTION)
        );
    }

    @Override
    public void onStop() {
        super.onStop();
        getActivity().stopService(intent);
        LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(broadcastReceiver);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_date_time, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        date = (TextView) view.findViewById(R.id.date);
        time = (TextView) view.findViewById(R.id.time);
    }

    /**
     * Update date and time to textviews
     */
    private void updateDataToUI(Intent intent) {
        date.setText(intent.getStringExtra(DateTimeService.DATE_STAMP));
        time.setText(intent.getStringExtra(DateTimeService.TIME_STAMP));
    }
}
    In DateTimeService,java, getting current system date/time and doing  this work after every 1 second execute a Runnable method with a Handler. Put data through Intent and LocalBroadcastManager instance :
 private Runnable sendUpdatesToUI = new Runnable() {
        public void run() {
            getDateTimes();
            handler.postDelayed(this, 1000); // 1 seconds
        }
    };

    public void getDateTimes() {
        calendar = Calendar.getInstance();
        int seconds = calendar.get(Calendar.SECOND);
        int minutes = calendar.get(Calendar.MINUTE);
        int hours = calendar.get(Calendar.HOUR);

        dateFormat = new SimpleDateFormat("dd-MM-yyyy");
        String formattedDate = "Current Date: " + dateFormat.format(calendar.getTime());
        String time = "Current Time: " + hours + ":" + minutes + ":" + seconds;

        //put info through intent
        intent = new Intent(DATE_TIME_ACTION);
        intent.putExtra(TIME_STAMP, time);
        intent.putExtra(DATE_STAMP, formattedDate);

        broadcastManager.sendBroadcast(intent);
    }
    Like above Service, initial BroadcastReceiver and start it through onCreate/onStartCommand.  Full code:
package info.devexchanges.services;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;

import java.text.SimpleDateFormat;
import java.util.Calendar;

/**
 * Created by Hong Thai
 */
public class DateTimeService extends Service {

    private Calendar calendar;
    private SimpleDateFormat dateFormat;
    private Intent intent;
    private final Handler handler = new Handler();
    private LocalBroadcastManager broadcastManager;

    public final static String TIME_STAMP = "Time";
    public final static String DATE_STAMP = "Date";
    public final static String DATE_TIME_ACTION = "info.devexchanges.dateservice";

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        broadcastManager = LocalBroadcastManager.getInstance(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        handler.removeCallbacks(sendUpdatesToUI);
        handler.postDelayed(sendUpdatesToUI, 1000); // 1 second
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        handler.removeCallbacks(sendUpdatesToUI);
    }

    private Runnable sendUpdatesToUI = new Runnable() {
        public void run() {
            getDateTimes();
            handler.postDelayed(this, 1000); // 1 seconds
        }
    };

    public void getDateTimes() {
        calendar = Calendar.getInstance();
        int seconds = calendar.get(Calendar.SECOND);
        int minutes = calendar.get(Calendar.MINUTE);
        int hours = calendar.get(Calendar.HOUR);

        dateFormat = new SimpleDateFormat("dd-MM-yyyy");
        String formattedDate = "Current Date: " + dateFormat.format(calendar.getTime());
        String time = "Current Time: " + hours + ":" + minutes + ":" + seconds;

        //put info through intent
        intent = new Intent(DATE_TIME_ACTION);
        intent.putExtra(TIME_STAMP, time);
        intent.putExtra(DATE_STAMP, formattedDate);

        broadcastManager.sendBroadcast(intent);
    }
}
 
    Our result when running:

5. Some necessary files

    Generating a BlankFragment by IDE to test other Fragment life cycle, ensure that data will be updated when DownloadFragment and DateTimeFragment was restored (data also not dissapear when changing page). After generate, we'll have this screen:
    A customize adapter based on FragmentPagerAdapter for ViewPager (ViewPagerAdapter.java):
package info.devexchanges.services.adapter;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

import info.devexchanges.services.ui.BlankFragment;
import info.devexchanges.services.ui.DateTimeFragment;
import info.devexchanges.services.ui.DownLoadFragment;

/**
 * Created by Hong Thai.
 */
public class ViewPagerAdapter extends FragmentPagerAdapter{

    public ViewPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int i) {
        if (i == 1) {
            return new DateTimeFragment();
        } else if (i == 0){
            return new DownLoadFragment();
        } else {
            return new BlankFragment();
        }
    }

    public CharSequence getPageTitle(int position) {
        if (position == 0) {
            return "Download";
        } else if (position == 1) {
            return "Date Time";
        } else {
            return "Blank Fragment";
        }
    }

    @Override
    public int getCount() {
        return 3;
    }
}

    Providing WRITE_EXTERNAL_STORAGE and INTERNET permissions, register our services in AndroidManifest:

6. Application output




Share


Previous post
« Prev Post
Next post
Next Post »