Android RecyclerView dynamically load more items when scroll to end with bottom ProgressBar

Android RecyclerView dynamically load more items when scroll to end with bottom ProgressBar

    Load more data when scrolling to bottom of list view/table view is one the most popular design style which available in a lot of application, for example: Facebook, Google+,... By my previous post, you've learn the way to do this trick with ListView. As you are already known, RecyclerView is a new component introduced in Android Lollipop, this component increases performances respect to ListView. Moreover, respect to ListView, RecyclerView is much more customizable.
    Today, with this post, I would like to talk about making an endless RecyclerView and when data is loading, it will show a ProgressBar at the bottom.

Adding dependencies to project

    The first work after creating a new Android Studio project is adding RecyclerView denpendency to your app level build.gradle. In this sample project, I set each RecyclerView item as a CardView, so you must add it's dependency, too:
dependencies {
    compile 'com.android.support:appcompat-v7:25.1.0'
    compile 'com.android.support:recyclerview-v7:25.1.0'
    compile 'com.android.support:cardview-v7:25.1.0'
}

Create layout files

    Now we must provide some necessary XML files to define layouts. Because of we will set a ProgressBar at the bottom of RecyclerView when data is loading, it will have two item types. The normal item that to show info of user and loading item that place at bottom to show progress bar.
    Layout for the normal item:
item_recycler_view_row.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    card_view:cardUseCompatPadding="true">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="?android:selectableItemBackground"
        android:padding="10dp">

        <TextView
            android:id="@+id/txt_email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            android:textSize="16sp"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/txt_phone"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/txt_email"
            android:textColor="@android:color/black"
            android:textSize="12sp" />
    </RelativeLayout>
</android.support.v7.widget.CardView>
    Layout for the loading item:
item_loading.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="wrap_content"
    android:orientation="vertical">

    <ProgressBar
        android:id="@+id/progressBar1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

</LinearLayout>
    And you must put a RecyclerView object to the main activity:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</RelativeLayout>

Create a POJO class

    We've just create all necessary layouts for this project, now please move to programmatically code. Firstly, provide a POJO class (model object) simply like this:
Contact.java
package info.devexchanges.endlessrecyclerview;

public class Contact {
    private String email;
    private String phone;

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

Define an interface for callbacks

    Now, make an interface named OnLoadMoreListener with an abstract method onLoadMore() which will be invoked when we scroll the RecyclerView to end:
OnLoadMoreListener.java
package info.devexchanges.endlessrecyclerview;

public interface OnLoadMoreListener {
    void onLoadMore();
}

Configuration in RecyclerView adapter

    In this RecyclerView adapter (I named as ContactAdapter), we have 2 item types then must create two ViewHolder like below:
// "Loading item" ViewHolder
private class LoadingViewHolder extends RecyclerView.ViewHolder {
        public ProgressBar progressBar;

        public LoadingViewHolder(View view) {
            super(view);
            progressBar = (ProgressBar) view.findViewById(R.id.progressBar1);
        }
    }

// "Normal item" ViewHolder
private class UserViewHolder extends RecyclerView.ViewHolder {
        public TextView phone;
        public TextView email;

        public UserViewHolder(View view) {
            super(view);
            phone = (TextView) view.findViewById(R.id.txt_phone);
            email = (TextView) view.findViewById(R.id.txt_email);
        }
    }
In this adapter, declare two constants that is delegate for two item type of RecyclerView:
private final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_LOADING = 1;
Provide an OnLoadMoreListener variable and set an "add method":
private OnLoadMoreListener onLoadMoreListener;
public void setOnLoadMoreListener(OnLoadMoreListener mOnLoadMoreListener) {
        this.onLoadMoreListener = mOnLoadMoreListener;
    }
In the constructor of this adapter class, we handle scroll event of the RecyclerView here. This is the most important step, firstly, you must get LayoutManager of RecyclerView, detecting scroll to bottom in onScroll():
    private boolean isLoading;
    private Activity activity;
    private List<Contact> contacts;
    private int visibleThreshold = 5;
    private int lastVisibleItem, totalItemCount;

    public ContactAdapter(RecyclerView recyclerView, List<Contact> contacts, Activity activity) {
        this.contacts = contacts;
        this.activity = activity;

        final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                totalItemCount = linearLayoutManager.getItemCount();
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
                if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {
                    if (onLoadMoreListener != null) {
                        onLoadMoreListener.onLoadMore();
                    }
                    isLoading = true;
                }
            }
        });
    }
Add some necessary methods to complete ContactAdapter:
    @Override
    public int getItemViewType(int position) {
        return contacts.get(position) == null ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_ITEM) {
            View view = LayoutInflater.from(activity).inflate(R.layout.item_recycler_view_row, parent, false);
            return new UserViewHolder(view);
        } else if (viewType == VIEW_TYPE_LOADING) {
            View view = LayoutInflater.from(activity).inflate(R.layout.item_loading, parent, false);
            return new LoadingViewHolder(view);
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof UserViewHolder) {
            Contact contact = contacts.get(position);
            UserViewHolder userViewHolder = (UserViewHolder) holder;
            userViewHolder.phone.setText(contact.getEmail());
            userViewHolder.email.setText(contact.getPhone());
        } else if (holder instanceof LoadingViewHolder) {
            LoadingViewHolder loadingViewHolder = (LoadingViewHolder) holder;
            loadingViewHolder.progressBar.setIndeterminate(true);
        }
    }

    @Override
    public int getItemCount() {
        return contacts == null ? 0 : contacts.size();
    }

    public void setLoaded() {
        isLoading = false;
    }

Activity programmatically code

    In onCreate() method of activity, we must call setOnLoadMoreListener() and get new data inside onLoadMore(). This is full code of the activity:
MainActivity.java
package info.devexchanges.endlessrecyclerview;

import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class MainActivity extends AppCompatActivity {

    private List<Contact> contacts;
    private ContactAdapter contactAdapter;
    private Random random;

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

        contacts = new ArrayList<>();
        random = new Random();

        //set dummy data
        for (int i = 0; i < 10; i++) {
            Contact contact = new Contact();
            contact.setPhone(phoneNumberGenerating());
            contact.setEmail("DevExchanges" + i + "@gmail.com");
            contacts.add(contact);
        }

        //find view by id and attaching adapter for the RecyclerView
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        contactAdapter = new ContactAdapter(recyclerView, contacts, this);
        recyclerView.setAdapter(contactAdapter);

        //set load more listener for the RecyclerView adapter
        contactAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
            @Override
            public void onLoadMore() {
                if (contacts.size() <= 20) {
                    contacts.add(null);
                    contactAdapter.notifyItemInserted(contacts.size() - 1);
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            contacts.remove(contacts.size() - 1);
                            contactAdapter.notifyItemRemoved(contacts.size());

                            //Generating more data
                            int index = contacts.size();
                            int end = index + 10;
                            for (int i = index; i < end; i++) {
                                Contact contact = new Contact();
                                contact.setPhone(phoneNumberGenerating());
                                contact.setEmail("DevExchanges" + i + "@gmail.com");
                                contacts.add(contact);
                            }
                            contactAdapter.notifyDataSetChanged();
                            contactAdapter.setLoaded();
                        }
                    }, 5000);
                } else {
                    Toast.makeText(MainActivity.this, "Loading data completed", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    private String phoneNumberGenerating() {
        int low = 100000000;
        int high = 999999999;
        int randomNumber = random.nextInt(high - low) + low;

        return "0" + randomNumber;
    }
}

Running application

    And this is output for our code:

Conclusions

    Over here, I've just present the way to put a progress bar at the bottom of RecyclerView and loading more data when scroll to end. This design is very popular in mobile application development, so I hope this post is helpful with your own work. Further, you can visit my previous post to learn solution with ListView or read related tutorial posts on other site like:
Android (Home screen) widget - Part 4: Update widget via Service

Android (Home screen) widget - Part 4: Update widget via Service

    The fact that, most of widgets that you can see on your device are auto-update information, for example: weather widget, location widget,... Service is the Context which is used here to change the widget data. With auto update feature, your widget become lively and useful!

    Today, by this post, I would like to present an updating widget, which displaying current time in HH:mm format.

Creating a Service class

    We'll get current time in the device in this class (which a subclass of Service). Override onStartCommand() method like this:
UpdateService.java
package info.devexchanges.updatewidget;

import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.widget.RemoteViews;

import java.util.Calendar;

public class UpdateService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

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

        String time = getCurrentDateTime();

        RemoteViews view = new RemoteViews(getPackageName(), R.layout.widget_updating);
        view.setTextViewText(R.id.txt_widget, time);
        ComponentName theWidget = new ComponentName(this, UpdatingWidget.class);
        AppWidgetManager manager = AppWidgetManager.getInstance(this);
        manager.updateAppWidget(theWidget, view);

        return super.onStartCommand(intent, flags, startId);
    }

    private String getCurrentDateTime() {
        Calendar c = Calendar.getInstance();
        int minute = c.get(Calendar.MINUTE);
        int hour = c.get(Calendar.HOUR_OF_DAY);

        return hour + ":" + minute;
    }
}
    As you can see at the code above, remember update data (current time) to a TextView in your widget after get time here!

Widget XML files

    Like another widgets at previous parts, your must create widget layout and property files (in XML).
    The layout for this widget:
layout\widget_updating.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/txt_widget"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:layout_margin="8dp"
        android:background="#09C"
        android:text="@string/appwidget_text"
        android:textColor="#ffffff"
        android:textSize="24sp"
        android:textStyle="bold|italic"/>

</RelativeLayout>
    And define the AppWidgetProviderInfo object in an XML like this:
xml\updating_widget_info.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_updating"
    android:minHeight="80dp"
    android:minWidth="80dp"
    android:previewImage="@mipmap/ic_launcher"
    android:resizeMode="vertical"
    android:updatePeriodMillis="0">
</appwidget-provider>

AppWidgetProvider class

    Service does not start by itself. We need to start the service (in every minute for this example) in the AppWidgetProvider.
    But why we do not just use updatePeriodMillis atribute? The official document say that:
If the device is asleep when it is time for an update (as defined by updatePeriodMillis), then the device will wake up in order to perform the update. If you don't update more than once per hour, this probably won't cause significant problems for the battery life. If, however, you need to update more frequently and/or you do not need to update while the device is asleep, then you can instead perform updates based on an alarm that will not wake the device. To do so, set an alarm with an Intent that your AppWidgetProvider receives, using the AlarmManager. Set the alarm type to either ELAPSED_REALTIME or RTC, which will only deliver the alarm when the device is awake. Then set updatePeriodMillis to zero ("0").
    In previous examples, we used getActivity() and getBroadcast() methods. This time we are going to use getService():
UpdatingWidget.java
package info.devexchanges.updatewidget;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;

public class UpdatingWidget extends AppWidgetProvider {
    private PendingIntent pendingIntent;

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
        final Intent i = new Intent(context, UpdateService.class);

        if (pendingIntent == null) {
            pendingIntent = PendingIntent.getService(context, 0, i, PendingIntent.FLAG_CANCEL_CURRENT);
        }
        manager.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), 60000, pendingIntent);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
    }
}
    The minimum interval time is 60000 milliseconds for AlarmManager. If you need to call your service less than 60 second with an alarm manager, please read this discussion on StackOverflow. But you should pay attention: this action drains the battery and makes users delete your app.

Running application

    The widget will be installed after you run the application. Drag it to Home screen and you'll have this result:

    I cropped the video to not keep you wait 4 minutes.

Conclusions

    Through 4 parts in this series, I hope you now have general knowledge about developing a widget in Android - which can be understand as an extension of the application. For further reading, check these helpful document and tutorial:
Android (Home screen) widget - Part 3: Configurable widget

Android (Home screen) widget - Part 3: Configurable widget

    In Part 2, I had presented one more example of widget: Broadcast Widget (which can update it's interface when clicked). Today, I would like to talk about a widget type that can be configurable at creation, this mean when you drag it to Home screen to use, a "configuration Activity" will be launched and you will perform a setting for your widget.

   In this sample project, we will allow users to choose a link and whenever it’s clicked we open this link on the browser.

Create layout for configuration Activity

    The most important work of creating this type of widget is developing the configuration Activity. Firstly, define a simple layout includes a Spinner to allow user selected one value from a set and a one value from a Button to confirm this work:
activity_config.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"
    android:padding="@dimen/activity_horizontal_margin">

    <Spinner
        android:id="@+id/spinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn_go"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/activity_horizontal_margin"
        android:text="@string/add" />
</LinearLayout>

Activity programmatically code

    Now, looking at this source code of ConfigActivity:
ConfigActivity.java
package info.devexchanges.configurablewidget;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.RemoteViews;
import android.widget.Spinner;

import java.util.ArrayList;

public class ConfigActivity extends AppCompatActivity {

    private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
    private AppWidgetManager widgetManager;
    private RemoteViews remoteViews;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setResult(RESULT_CANCELED);

        setContentView(R.layout.activity_config);

        final Spinner spinner = (Spinner)findViewById(R.id.spinner);
        View btnCreate = findViewById(R.id.btn_go);

        //create data
        ArrayList<String> spnOptions = new ArrayList<>();
        spnOptions.add("Go to my site");
        spnOptions.add("Go to Google page");

        //set adapter for the spinner
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, spnOptions);
        spinner.setAdapter(adapter);

        //initializing RemoteViews and AppWidgetManager
        widgetManager = AppWidgetManager.getInstance(this);
        remoteViews = new RemoteViews(this.getPackageName(), R.layout.widget_configurable);

        // Find the widget id from the intent
        Bundle extras = getIntent().getExtras();
        if (extras != null) {
            mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
        }
        if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
            finish();
            return;
        }
        btnCreate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String selectedUrl;
                if (spinner.getSelectedItemPosition() == 0) {
                    // Go to my website with this selection (position = 1)
                    selectedUrl = "http://www.devexchanges.info";
                } else {
                    selectedUrl = "https://www.google.com";
                }
                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(selectedUrl));
                PendingIntent pending = PendingIntent.getActivity(ConfigActivity.this, 0, intent, 0);
                remoteViews.setOnClickPendingIntent(R.id.text_view, pending);
                if (spinner.getSelectedItemPosition() == 0) {
                    remoteViews.setTextViewText(R.id.text_view, "Click to visit my site");
                } else {
                    remoteViews.setTextViewText(R.id.text_view, "Click to visit Google");
                }
                widgetManager.updateAppWidget(mAppWidgetId, remoteViews);
                Intent resultValue = new Intent();

                // Set the results as expected from a 'configure activity'.
                resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
                setResult(RESULT_OK, resultValue);
                finish();
            }
        });
    }
}
    In onCreate() method, the first thing we do is setting setResult(RESULT_CANCELED). Why? Android triggers the configuration Activity that belongs to your widget and awaits result data. If the user did not configure as we expected, let’s say she pressed back button without entering a data, we don’t need to create a widget.
    At the Spinner, we set data to make 2 options for user to choose (go to DevExchanges home page or Google), after click the Button, we update TextView content on the widget and set the Set the RemoteViews to based on appWidgetIds.

Modifying the widget xml

    The last thing we do is modify the XML of the widget. With that modification, Android OS will know this widget has a configuration Activity. So before creating the widget, it will trigger the Activity:
res\xml\configurable_widget_info.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:configure="info.devexchanges.configurablewidget.ConfigActivity"
    android:initialLayout="@layout/widget_configurable"
    android:minHeight="40dp"
    android:minWidth="40dp"
    android:previewImage="@mipmap/ic_launcher"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="86400000" />
    As you notice we didn’t talk about widget class yet. We do not need to add any code for widget class because all actions done by ConfigActivity. But we have to create it anyway:
ConfigurableWidget.java
package info.devexchanges.configurablewidget;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;

public class ConfigurableWidget extends AppWidgetProvider {
    
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {

    }
}
    And this is the layout file for our widget:
res\layout\widget_configurable.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:padding="8dp"
        android:background="@color/colorPrimary"
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_gravity="center"
        android:textColor="@android:color/white"
        android:textStyle="bold" />

</RelativeLayout>

Running the application

    Before launching the app, make sure you add ConfigurableWidget as a receiver to your AndroidManifest.xml like other previous examples:
<application
        android:allowBackup="true"
        ....>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver android:name=".ConfigurableWidget">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/configurable_widget_info" />
        </receiver>

        <activity android:name=".ConfigActivity">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
            </intent-filter>
        </activity>
    </application>

    After app installed, move to "WIDGETS" tab, you will see it:
    And when you drag it to Home page to use, the configuration activity will be launched:
    When click on it:

     If you select other options at the configuration activity,  the widget text will be different:

Conclusions

    I've just provided one more example about Android widget, hope you can understand the way to configure widget before using it. Up to next part, I will talk about updating widget via a Service - the most popular and important feature of this topic! Coming soon!

Android Basic Training Course: Creating a StackView

Android Basic Training Course: Creating a StackView

    Stack is a common design that we can see at fronted-end programming including mobile app development. When using Android phone, you can realize that showing "recent apps" is a type of stack view design:
    This design is very diverse but by this post, just about "Android basic development", I would like to talk about using StackView widget. Like ListView or GridView, it's a subclass of AdapterView, so it has the same operation mechanism (adapter of a "list" of object). Now, following this tutorial step by step, you will understand the way to use it!

Preparing some drawables resource

    The fact that stack view is usually used for display "stack of images" or something that similarity. So, after start a new Android Studio project, please add some own images to drawable folder. Make sure that these images have the same size:

Put StackView to your Activity layout

    Just including a StackView in your activity layout file like this:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="info.devexchanges.stackview.MainActivity">

    <StackView
        android:id="@+id/stack_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" />

</RelativeLayout>

Create layout for each StackView item

    As noted above, Stack view is usually used for display list of images, so I will make a layout which contains an ImageView to show a drawable resource and a TextView to present it's name for each StackView item like this:
item_stack.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/imageView"
        android:contentDescription="@null"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image_1"
        android:layout_gravity="center_horizontal" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="@dimen/activity_horizontal_margin"
        android:textColor="@android:color/holo_blue_dark"
        android:textStyle="bold" />

</LinearLayout>

Create a POJO class

    Java object for each Stack view item:
StackItem.java
package info.devexchanges.stackview;

public class StackItem {

    private int drawableId;
    private String imageName;

    public StackItem(int id, String imageName) {
        this.imageName = imageName;
        this.drawableId = id;
    }

    public String getImageName() {
        return imageName;
    }

    public int getDrawableId() {
        return drawableId;
    }
}

Create a StackView adapter class

    This is the most important step of this tutorial. Like ListView, we can create a subclass of ArrayAdapter or BaseAdapter to build the adapter class which holding and displaying data. I also provide a ViewHolder to make convertView clearly:
StackAdapter.java
package info.devexchanges.stackview;

import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

public class StackAdapter extends ArrayAdapter<StackItem> {

    private List<StackItem> items;
    private Context context;

    public StackAdapter(Context context, int textViewResourceId, List<StackItem> objects) {
        super(context, textViewResourceId, objects);
        this.items = objects;
        this.context = context;
    }

    @NonNull
    @SuppressLint("InflateParams")
    public View getView(int position, View convertView, @NonNull ViewGroup parent) {
        ViewHolder holder; //initialize a null ViewHolder object
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        // If holder not exist then locate all view from UI file
        if (convertView == null) {
            // inflate UI from XML file
            convertView = inflater.inflate(R.layout.item_stack, parent, false);
            // get all UI view
            holder = new ViewHolder(convertView);
            // set tag for holder
            convertView.setTag(holder);
        } else {
            // if holder created, get tag from view
            holder = (ViewHolder) convertView.getTag();
        }
        StackItem stackItem = items.get(position);

        //set stack item data to views
        holder.image.setImageResource(stackItem.getDrawableId());
        holder.imageName.setText(stackItem.getImageName());

        return convertView;
    }

    private class ViewHolder {
        private TextView imageName;
        private ImageView image;

        public ViewHolder(View view) {
            this.image = (ImageView) view.findViewById(R.id.imageView);
            this.imageName = (TextView) view.findViewById(R.id.textView);
        }
    }
}

Activity programmatically code

    There is nothing special in the main activity code. Just initializing an adapter with available data and attaching it to the StackView:
MainActivity.java
package info.devexchanges.stackview;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.StackView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private final int[] IMAGE_IDs = {R.drawable.image_1, R.drawable.image_2, R.drawable.image_3,
            R.drawable.image_4, R.drawable.image_5, R.drawable.image_6};

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

        StackView stackView = (StackView) findViewById(R.id.stack_view);

        ArrayList<StackItem> stackItems = new ArrayList<>();
        for (int imageId : IMAGE_IDs) {
            String name = getResources().getResourceEntryName(imageId);
            stackItems.add(new StackItem(imageId, name));
        }

        StackAdapter stackAdapter = new StackAdapter(this, R.layout.item_stack, stackItems);
        stackView.setAdapter(stackAdapter);
    }
}

Running the application

    Our output will be like this:

The weakness of the "default" StackView

    This "official" widget of Android SDK has the biggest weakness is swipe listener is absent, detect this user action is not a easily. Selected item event still not working well, either. I recommend to use a third-party library to make this layout better in programming. These are some of them:
I also had a post about creating Swipe Card Stack, read this tutorial HERE!




Android (Home screen) Widget - Part 2: Broadcast Widget

Android (Home screen) Widget - Part 2: Broadcast Widget

    Through Part 1, you've learned some basic knowledge about widget in Android development and the way to make a simple one. In this post, I would like to present a widget can update it's information when clicked (every click on the widget will send a broadcast and show current time at the TextView).

    You have learned all steps of widget creation. We will not be repeating these steps, but now, please pay attention at RemoteView of the widget.

About the RemoteView

    In Part 1, I have not to talk you about RemoteViews and widget layout yet, now I will explain some main features of this property.
    Of course, you must define an initial layout for your widget in XML and save it in the project's res/layout/ directory. In this example, it will be like this:
widget_broadcast.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/txt_widget"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:text="Click to get current time"
        android:textColor="#ffffff"
        android:textSize="20sp"
        android:textStyle="bold|italic"/>

</LinearLayout>
    Creating the widget layout is simple if you're familiar with Layouts. However, you must be aware that widget layouts are based on RemoteViews, which do not support every kind of layout or view widget.
    A RemoteViews object (and, consequently, a widget) can support the following layout classes:
    And the following widget classes: AnalogClock, Button, Chronometer, ImageButton, ImageView, ProgressBar, TextView, ViewFlipper, ListView, GridView, StackView, AdapterViewFlipper.
    Descendants of these classes are not supported. RemoteViews also supports ViewStub, which is an invisible, zero-sized View you can use to lazily inflate layout resources at runtime.

Updating when clicked - programmatically code

    You always have to create a subclass of AppWidgetProvider to managing the widget lifecycle. Please take a look at this class first:
BroadcastWidget.java
package info.devexchanges.broadcastwiget;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;

import java.util.Calendar;

public class BroadcastWidget extends AppWidgetProvider  {
    private static final String ACTION_BROADCASTWIDGETSAMPLE = "ACTION_BROADCASTWIDGETSAMPLE";

    private void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
                                int appWidgetId) {

        // Build the RemoteViews object
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_broadcast);

        // Create an Intent which is pointing this class
        Intent intent = new Intent(context, BroadcastWidget.class);
        intent.setAction(ACTION_BROADCASTWIDGETSAMPLE);
        // And this time we are sending a broadcast with getBroadcast
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        // Update widget when clicked
        views.setOnClickPendingIntent(R.id.txt_widget, pendingIntent);
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        if (ACTION_BROADCASTWIDGETSAMPLE.equals(intent.getAction())) {
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_broadcast);
            views.setTextViewText(R.id.txt_widget, getCurrentDateTime());

            // This time we don't have widgetId. Reaching our widget with that way
            ComponentName appWidget = new ComponentName(context, BroadcastWidget.class);
            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

            // Update the widget
            appWidgetManager.updateAppWidget(appWidget, views);
        }
    }

    private String getCurrentDateTime() {
        Calendar c = Calendar.getInstance();
        int second = c.get(Calendar.SECOND);
        int minute = c.get(Calendar.MINUTE);
        int hour = c.get(Calendar.HOUR_OF_DAY);

        return hour + ":" + minute + ":" + second;
    }
}
    In previous example we used getActivity(), this time we are going to use getBroadcast() when instruct the AppWidgetManager to update the widget.
    To get current time, I use java.util.Calendar, on other way, you can use a Date instance!
    When user clicked at the TextView, onReceive() will be called and in this method, we get current time and shown by this TextView.
    For another step about creating this widget, please read at Part 1.
    NOTE: You can send whatever you want with broadcast and you can catch the broadcast wherever you want.

Running application

    Just keeping the default code of MainActivity, running this app, you'll see "Hello, World" text:

    And the widget is installed, you can find it at WIDGETS tab of the device. Drag it to Home screen to use:
    And when you click on the widget text:

Conclusions

    I've just introduce an another example/feature of Android app widget: updating information when user clicked. Hope this post is helpful with all readers in your own work. Up to Part 3, I will talk about configurable widget with an Activity, coming soon!
   
    Update: Part 3 - Configurable widget now available HERE!

Android (Home screen) Widget - Part 1: Simple widget

Android (Home screen) Widget - Part 1: Simple widget

    Widgets can be thought of as a small window or controller for an Android app that can be embedded in another application (like the home screen). They can be very useful, allowing users to view or control an app without actually launching it. This is official definition of widget in Android:
Widgets are an essential aspect of home screen customization. You can imagine them as “at-a-glance” views of an app’s most important data and functionality that is accessible right from the user’s home screen.

Types of widget in Android

    Basically, it's can be divided to 4 types of widget in Android:
  • Information widget: display a few crucial information elements that are important to a user and track how that information changes over time. Good examples for information widgets are weather widgets, clock widgets or sports score trackers. Touching information widgets typically launches the associated app and opens a detail view of the widget information. For example: weather widget.
  • Collection widget: collection widgets specialize in displaying multitude elements of the same type, such as a collection of pictures from a gallery app, a collection of articles from a news app or a collection of emails/messages from a communication app. For example: email widget.
  • Control widget: the main purpose of a control widget is to display often used functions that the user can trigger right from the home screen without having to open the app first. For example: change wifi, bluetooth,...state widget.
  • Hybrid widget: While all widgets tend to gravitate towards one of the three types described above, many widgets in reality are hybrids that combine elements of different types. For the purpose of your widget planning, center your widget around one of the base types and add elements of other types if needed. For example: music player widget.

Components of a widget

    There are 3 components of a widget that you must implement:
  • An AppWidgetProviderInfo object: describes the metadata for an app widget, such as the app widget's layout, update frequency, and the AppWidgetProvider class. This should be defined in XML.
  • AppWidgetProvider class implementation: defines the basic methods that allow you to programmatically interface with the App Widget, based on broadcast events. Through it, you will receive broadcasts when the App Widget is updated, enabled, disabled and deleted.
  • View layout: defines the initial layout for the App Widget, defined in XML.
    Additionally, you can implement an app widget configuration Activity. This is an optional Activity that launches when the user adds your app widget and allows him or her to modify app widget settings at create-time. The following sections describe how to set up each of these components.
    Now, please follow these steps to create a very simple widget which open my website when click on it!

Create a layout for the widget

    Providing a simple layout for the widget which only includes a TextView:
res\layout\widget_simple.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">

    <TextView
        android:id="@+id/txtWidget"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorPrimary"
        android:gravity="center"
        android:text="Click to visit my site"
        android:textColor="#ffffff"
        android:textSize="24sp"
        android:textStyle="bold" />


</LinearLayout>
This layout is displayed as a widget on user’s Home screen.

Create an XML file that defines widget properties

    The AppWidgetProviderInfo defines the essential qualities of a widget, such as its minimum layout dimensions, its initial layout resource, how often to update the widget, and (optionally) a configuration Activity to launch at create-time. Define the AppWidgetProviderInfo object in an XML resource using a single <appwidget-provider> element and save it in the project's res/xml/ folder:
widget_provider_info.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_simple"
    android:minHeight="60dp"
    android:minWidth="100dp"
    android:previewImage="@mipmap/ic_launcher"
    android:resizeMode="horizontal|vertical"
    android:updatePeriodMillis="0"/>
    Take a look at some attributes of <appwidget-provider> above:
  • minHeight and minWidth: Every 60dp means 1 cell in android Home screen. For this example, the widget takes min 1x1 cell(s).
  • initialLayout: reference for the widget layout(that we already created).
  • previewImage: the image that will be shown on android’s widget select screen. We can not draw a layout for preview. We have to set an image. For simple, I use ic_launcher of the application.
  • resizeMode: the configuration for resizing the widget.
  • updatePeriodMillis: the widget’s update method is called when the specified time is reached in a millisecond.

Create a class for the widget lifecycle

    We now need to create a subclass of AppWidgetProvider (it is also a subclass of BroadcastReceiver) so essentially, your widget class is a receiver class. There are some important methods you must/may override:
  • onUpdate(): called to update the app widget at intervals defined by the updatePeriodMillis attribute.
  • onEnabled(): called when an instance the app widget is created for the first time.
  • onDisabled(): called when the last instance of your app widget is deleted from the app widget host.
  • onDeleted(): called every time an app widget is deleted from the host.
  • onAppWidgetOptionsChanged(): called when the widget is first placed and also whenever the widget is resized.
  • onReceive(): called for every broadcast and before each of the above callback methods.
    In this Part 1, I will make a simple widget, so I only override onUpdate() like this:
SimpleWidget.java
package info.devexchanges.simplewidget;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.widget.RemoteViews;

public class SimpleWidget extends AppWidgetProvider {
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
        // Construct the RemoteViews object
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_simple);

        // Create an Intent object includes my website address
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.devexchanges.info/"));
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

        //handle click event of the TextView (launch browser and go to my website)
        views.setOnClickPendingIntent(R.id.txtWidget, pendingIntent);
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }
}
    As a receiver class, your must register it to your AndroidManifest.xml:
AndroidManifest.xml
<application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver android:name=".SimpleWidget">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_provider_info"/>
        </receiver>
    </application>

Running the application

    A widget always "attach" with the application. Make sure that you have the default Activity (usually named as MainActivity) when you created a new Android Studio project. Run this application, the widget will be installed and the MainActivity is also launched (with "Hello world"!):
    Move to "WIDGETS" tab on your device, you will see your own widget:
    In order to use it, please hold and drag it to the Home screen:
    And when click the widget:
    Remember that if you uninstall the application, the widget will be removed!

Conclusions

    Now, you're learned about Android widget basic concept and the way to make a simple widget. In Part 2, I will talk about making a Broadcast widget - a widget that have update data when clicked (coming soon!). Further, to deep understanding about widget design and development in Android, please read it's official documents:
    UPDATE: Part 2 now available HERE!