ZOFTINO.COM android and web dev tutorials

Android Widget Example

One of the excellent features of Android is that app’s information and features can be displayed as mini apps on home screens. These mini apps are called widgets. Widgets make it easy for users to access frequently changing information or widely used features of apps.

This tutorial explains how to create a widget taking location widget as an example. It also shows how to create widget configuration or settings option.

To know the basic concepts of widgets and how to create simple widget, see android widgets basics tutorial.

Android Widget Example

The example widget displays current location name. Current location name based on the device location is obtained using Google places SDK for Android.

When widget needs to refresh the data as per widget configuration, it creates a background job which fetches current place name and updates the widget with current place name.

When widget is placed on the screen (when it is created), settings screen is displayed. Setting screen allows users to set background opacity and text colors. User can also get to settings screen by clicking the widget.

The app which contains the location widget has a screen which also displays current location. The app uses RxJava to get the location data and display in UI.

android location widget example

Project Setup

Location name widget uses Google places sdk for android, RxJava and workmanager. So, add those libraries to module build.grale file.

implementation 'android.arch.work:work-runtime:1.0.0-rc02'

implementation 'com.google.android.libraries.places:places:1.0.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.7'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'com.google.guava:guava:27.0.1-android'

To use Google places API, you need to obtain key, enable billing and enable places API. Follow the instructions provided here.

Location App Main Activity

The activity just displays location name in a text view. The activity prompts for android.permission.ACCESS_FINE_LOCATION permission if it is not granted as the permission is required for calling Google places API.

Google places API is called using RxJava from the app so that API call can run in the background thread and results can be updated in UI on the main thread once the place response is received. Place name is store in shared preferences.

MainActivity

public class MainActivity extends BaseActivity {

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

        loactionName = findViewById(R.id.location_name);

        //google places api requires location access permission
        checkPermission(ACCESS_FINE_LOCATION);

        getPlaces();
    }
}

Since widget configuration activity also uses permission function and the code related to Google places API, the common code is added to BaseActivity class which is extended by both main activity and widget configuration activity classes.

In the base activity on create method, location data API and shared preferences objects are instantiated. It contains methods to check location permission and to call location API in the background using RxJava.

BaseActivity

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;

import static android.Manifest.permission.ACCESS_FINE_LOCATION;

public class BaseActivity extends AppCompatActivity {

    protected LocationDataProviderInterface locationDataProvider;
    protected TextView loactionName;
    protected SharedPreferences sharedPref;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //instance of location provider
        // which uses google places api to get location data
        locationDataProvider = new LocationDataProvider(this);

        //shared preferences used for storing settings
        //one setting for all instances of the widget
        sharedPref = getApplicationContext().getSharedPreferences(
                getString(R.string.location_pref), Context.MODE_PRIVATE);
    }

    //gets place info in the background using rxjava
    protected void getPlaces(){
        Observable.just(locationDataProvider.getPlaces()).subscribeOn(Schedulers.computation())
                .flatMap(s -> {
                    while(!s.isComplete()){
                        Thread.sleep(100);
                    }
                    return Observable.just(s.getResult().getPlaceLikelihoods()
                            .get(0).getPlace().getName());
                }).observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::handleResults, this::handleError );
    }

    protected void handleResults(String place){
        loactionName.setText(place);

        SharedPreferences.Editor editor = sharedPref.edit();
        editor.putString(getString(R.string.place), place);
        editor.commit();
    }

    protected void handleError(Throwable t){
        Log.e("place api error", ""+ t.toString());
        loactionName.setText("couldn't get place info");
    }

    protected boolean checkPermission(String permission) {
        boolean hasPermission =
                ContextCompat.checkSelfPermission(this, permission)
                        == PackageManager.PERMISSION_GRANTED;
        if (!hasPermission) {
            ActivityCompat.requestPermissions(this, new String[]{permission}, 0);
        }
        return hasPermission;
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[],
                                           int[] grantResults) {
        if (requestCode == 0) {
            int i = 0;
            for(String perm : permissions){
                if(perm.equals(ACCESS_FINE_LOCATION) &&
                        grantResults[i] == PackageManager.PERMISSION_GRANTED){
                    getPlaces();
                }
                i++;
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

AppWidgetProvider

The class which provides widget UI and its functionality is a service and it is created by extending AppWidgetProvider class and overriding onEnabled, onDisabled, onUpdate or onReceive methods as required.

Our location widget provider’s onEnabled method, which is called when widget is instantiated, contains code related to location access permission, if ACCESS_FINE_LOCATION permission is not granted, main activity will be started. The main activity will prompt users asking to allow or deny the permission.

The method which gets called every time the widget needs to be refreshed is onUpdate method. In this method, a work which will call Google places API is scheduled using work manager.

On receiving the response from location API, the task broadcasts a message to the widget provider service. The service’s onReceive method handles the intent. It will retrieve the location data from the intent and refresh the widget with the location name received.

One important behavior that needs to be noticed when using work manager is that it enables and disables components depending on the usage to release resources. In this process work manager sends ACTION_PACKAGE_CHANGED broadcast. In our example after a work is scheduled to call places API, the broadcast will result in a call to onUpdate() of app widget provider creating an infinite loop. To fix this issue, we put a restriction of 5 minutes lapse between API calls. The last time at which the api is called is stored in shared preferences.

Refreshing the Widget

When widget is created or refreshed, location name needs to be populated and pending intent which get fired when widget is clicked to show settings screen needs to be added. Since our example provides widget-settings feature, the user selected settings need to be applied to the UI elements of the widget when it is create and updated. All this code related to widget views construction exists in the widget provider class.

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.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.widget.RemoteViews;

import java.util.Date;

import static android.Manifest.permission.ACCESS_FINE_LOCATION;

public class LocationAppWidgetProvider extends AppWidgetProvider {

    public static final String DISPLAY_PLACE = "com.zoftino.locationinfowidget.DISPLAY_PLACE";
    public static final String WIDGET_CLICK = "com.zoftino.locationinfowidget.WIDGET_CLICK";
    public static final String PLACE_NAME = "PLACE_NAME";
    public static final String DEFAULT_LOCATION = "Getting Place Name";

    @Override
    public void onEnabled(Context context) {
        LocalBroadcastManager.getInstance(context)
                .registerReceiver(this, new IntentFilter(DISPLAY_PLACE));
        LocalBroadcastManager.getInstance(context)
                .registerReceiver(this, new IntentFilter(WIDGET_CLICK));

        //location access permission is required
        if (!(ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED)) {
            Intent mainActivity = new Intent(context, MainActivity.class);
            context.startActivity(mainActivity);
        }
    }

        @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager,
                         int[] appWidgetIds) {
            //this logic prevents infinite loop which is caused due to
            //the broadcast of ACTION_PACKAGE_CHANGED in the process of
            // idle resource cleanup by the work manager
            if(isLocationWorkCalledRecently(context.getApplicationContext())){
                return;
            }
            updateLocationWidget(context.getApplicationContext(), null);
            //schedule location fetcher job
            LocationScheduler.scheduleGetPlacesWork(appWidgetIds);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);

        if (intent.getAction().equals(DISPLAY_PLACE)) {
            //display place after fetching
            String placeName = intent.getStringExtra(PLACE_NAME);
            updateLocationWidget(context.getApplicationContext(), placeName);

        }else if (intent.getAction().equals(WIDGET_CLICK)){
            //start settings activity
            Intent settings = new Intent(context, WidgetConfigurationActivity.class);
            settings.setAction(WIDGET_CLICK);
            context.startActivity(settings);
        }
    }

    //update location widgets
    public static void updateLocationWidget(Context context, String placeName){
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        int appWidgetId[] = appWidgetManager
                .getAppWidgetIds(new ComponentName(context,
                        LocationAppWidgetProvider.class));
        RemoteViews rviews = getRemoteViewsOfWidget(context, appWidgetId, placeName);
        appWidgetManager.updateAppWidget(appWidgetId, rviews);
    }

    private static RemoteViews getRemoteViewsOfWidget(Context context,
                                                      int appWidgetId[], String placeName){
        RemoteViews rviews = new RemoteViews(context.getPackageName(),
                R.layout.location_widget_layout);
        //add pending intent to handle widget click events
        setWidgetClickPendingIntent(context, appWidgetId, rviews);

        //apply current settings
        applySettings(context, appWidgetId, rviews);

        //display current place
        populateData(context, rviews, placeName);

        return rviews;
    }

    //sets pending intent which gets fired on clicking widget
    private static void setWidgetClickPendingIntent(Context context, int appWidgetId[],
                                             RemoteViews rviews){
        Intent intent = new Intent(context, LocationAppWidgetProvider.class);
        intent.setAction(WIDGET_CLICK);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

        rviews.setOnClickPendingIntent(R.id.container, pendingIntent);
    }

    //applies settings to widgets
    private static void applySettings(Context context, int appWidgetId[], RemoteViews rviews) {
        SharedPreferences sharedPref = context.getSharedPreferences(
                context.getString(R.string.location_pref), Context.MODE_PRIVATE);

        int widgetBackground = 255 - sharedPref.getInt(
                context.getString(R.string.widget_background), 255);
        Color bg = Color.valueOf(context.getResources().getColor(R.color.widgetBg, null));
        widgetBackground = Color.argb((float)widgetBackground, bg.red(), bg.green(), bg.blue() );

        rviews.setInt(R.id.container, "setBackgroundColor", widgetBackground);

        int textcolor = sharedPref.getInt(context.getString(R.string.widget_color), R.id.black);
        rviews.setTextColor(R.id.location_name, ContextCompat.getColor(context, textcolor));
    }

    //populates widgets with current place name
    private static void populateData(Context context, RemoteViews rviews, String placeName) {
        SharedPreferences sharedPref = context.getSharedPreferences(
                context.getString(R.string.location_pref), Context.MODE_PRIVATE);
        if(placeName == null){
            placeName = sharedPref.getString(context.getString(R.string.place), placeName);
        }else{
            SharedPreferences.Editor editor = sharedPref.edit();
            editor.putString(context.getString(R.string.place), placeName);
            editor.commit();
        }

        if(placeName == null || placeName.isEmpty()){
            placeName = DEFAULT_LOCATION;
        }
        rviews.setTextViewText(R.id.location_name, placeName);
    }

    //this is used to prevent loop
    //due to work manager ACTION_PACKAGE_CHANGED broadcast
    //which results in a call to onUpdate method
    private static boolean isLocationWorkCalledRecently(Context context){
        SharedPreferences sharedPref = context.getSharedPreferences(
                context.getString(R.string.location_pref), Context.MODE_PRIVATE);

        long lastApiCallTime = sharedPref.getLong(
                context.getString(R.string.last_api_call), 0);

        if(lastApiCallTime != 0){
           long currentTime = new Date().getTime();
            double mins = (currentTime - lastApiCallTime)/60000;
            if(mins > 5){
                sharedPref.edit().putLong(context.getString(R.string.last_api_call),
                        currentTime).commit();
                return false;
            }
        }else{
            sharedPref.edit().putLong(context.getString(R.string.last_api_call),
                    new Date().getTime()).commit();
        }
        return true;
    }
}

Widget Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/container"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/widgetBg">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:textStyle="bold"
        android:textColor="@color/widgetTxt"
        android:textSize="20dp"
        android:text="Current Location"/>
    <TextView
        android:id="@+id/location_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:textStyle="bold"
        android:textColor="@color/widgetTxt"
        android:textSize="20dp"/>

</LinearLayout>

Starting Background Task from Widget

The example uses work manager to schedule a background job which will fetch location name.

public class LocationScheduler {

    public static void scheduleGetPlacesWork(int appWidgetId[]) {
        Data source = new Data.Builder()
                .putIntArray("widgetId", appWidgetId)
                .build();

        OneTimeWorkRequest workRequest =
                new OneTimeWorkRequest.Builder(LocationWork.class)
                        .setInputData(source)
                        .build();

        WorkManager.getInstance().enqueue(workRequest);
    }
}

WorkManger Work

The work class calls places API to get location information and updates the widget with the results.

import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager;

import com.google.android.gms.tasks.Task;
import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;

import androidx.work.Worker;
import androidx.work.WorkerParameters;


public class LocationWork extends Worker {

    private Context ctx;
    private LocationDataProviderInterface locationDataProvider;
    private int appWidgetId[];

    public LocationWork(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
        ctx = context;

        appWidgetId = workerParams.getInputData().getIntArray("widgetId");
    }

    @NonNull
    @Override
    public Worker.Result doWork() {
        getPlaces(new LocationDataProvider(ctx), appWidgetId);
        return Worker.Result.success();
    }

    private void getPlaces(LocationDataProvider locationDataProvider, int appWidgetId[]) {
        Task<FindCurrentPlaceResponse> placeTask = locationDataProvider.getPlaces();
        placeTask.addOnSuccessListener(
                (response) -> {
                    String pname = response.getPlaceLikelihoods().get(0).getPlace().getName();
                    displayPlaceInWidget(pname, appWidgetId);
                });
        placeTask.addOnFailureListener(
                (exception) -> {
                    exception.printStackTrace();
                    displayPlaceInWidget("Couldn't get", appWidgetId);
                });
    }

    private void displayPlaceInWidget(String placeName, int appWidgetId[]) {

        //broadcast message to widget provider to update widget with
        //the place data received from places API
        Intent widgetIntent = new Intent();

        widgetIntent.setAction(LocationAppWidgetProvider.DISPLAY_PLACE);
        widgetIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                appWidgetId);
        widgetIntent.putExtra(LocationAppWidgetProvider.PLACE_NAME,
                placeName);

        LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(widgetIntent);
    }
}

Updating Widget from Background Task

To update the widget with results from the background job, a broadcast message is sent to the widget provider from the background work. The onReceive method of widget provider updates the widget with the received data from the API, see above code for details.

Places API

Following class contains code related to Google places API.

import android.content.Context;

import com.google.android.gms.tasks.Task;
import com.google.android.libraries.places.api.Places;
import com.google.android.libraries.places.api.model.Place;
import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest;
import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;
import com.google.android.libraries.places.api.net.PlacesClient;

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

public class LocationDataProvider implements LocationDataProviderInterface {

    private PlacesClient placesClient;

    public LocationDataProvider(Context ctx){
        if (!Places.isInitialized()) {
            String gApiKey = ctx.getString(R.string.g_api_key);
            Places.initialize(ctx, gApiKey);
        }
        placesClient = Places.createClient(ctx);

    }

    @Override
    public Task<FindCurrentPlaceResponse> getPlaces() {
        List<Place.Field> fields = new ArrayList<>();
        fields.add(Place.Field.NAME);
        FindCurrentPlaceRequest currentPlaceRequest =
                FindCurrentPlaceRequest.newInstance(fields);

        Task<FindCurrentPlaceResponse> currentPlaceTask;
        try{
            currentPlaceTask =
                    placesClient.findCurrentPlace(currentPlaceRequest);
        }catch (SecurityException e){
            return null;
        }

        return currentPlaceTask;
    }

    @Override
    public void getPlaceDetails() {

    }
}

Widget Configuration

The example shows how to add widget configuration feature. When widget is created, user is presented with widget configuration screen where user can select settings for widget. In the location widget example, widget background transparency and text color are configurable.

To provide widget configuration feature when widget is instantiated, you need to define configuration layout and activity for UI and behavior of widget configuration and then add the activity as a value to android:configure attribute in the widget provider xml definition.

Our example widget configuration UI contains radio buttons for allowing users to select widget text color and SeekBar for allowing users to select widget background transparency. The widget configuration UI also displays widget with changing background transparency and text colors as user selects them on the screen.

On closing the configuration window, all the selected settings will be saved in shared preferences and applied to widget instances.

android widget configuration example

Widget Settings Option

User can also get to widget configuration screen to change widget setting by clicking the widget. When widget is constructed, pending intent is added to the root view of the widget layout. The intent gets fired when the root view is clicked and the handler will start configuration activity in response to the event.

Intent intent = new Intent(context, LocationAppWidgetProvider.class);
intent.setAction(WIDGET_CLICK);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

rviews.setOnClickPendingIntent(R.id.container, pendingIntent);

Widget Configuration Activity

The activity is used for configuring location widget when it is instantiated and it is clicked.


import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.SeekBar;

import static android.Manifest.permission.ACCESS_FINE_LOCATION;

public class WidgetConfigurationActivity extends BaseActivity {

    private LinearLayout container;
    private SeekBar seekBar;
    private RadioGroup rg;

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

        loactionName = findViewById(R.id.location_name);
        container = findViewById(R.id.container);

        seekBar = findViewById(R.id.widgetBackground);
        rg = findViewById(R.id.widgetColor);

        //populate setting-UI elements with the selected values or default values
        seekBar.setProgress(sharedPref.getInt(getString(R.string.widget_background), 0));
        rg.check(sharedPref.getInt(getString(R.string.widget_color_button), R.id.black));

        //apply settings to the widget
        //helps in viewing how widget looks with current settings.
        applySettings();

        //handle widget background settings and store in shared preferences
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                //save the selected background
                SharedPreferences.Editor editor = sharedPref.edit();
                editor.putInt(getString(R.string.widget_background), i);
                editor.commit();

                applySettings();
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        });

        //google places api requires location access peremission
        checkPermission(ACCESS_FINE_LOCATION);

        //get current place data
        getPlaces();

    }

    //handle text color setting and store in shared preferences
    public void onChooseColor(View view) {
        if (!((RadioButton) view).isChecked()) {
            return;
        }

        int color = android.R.color.black;
        int selectedRadio = R.id.black;

        switch (view.getId()) {
            case R.id.red:
                color = android.R.color.holo_red_dark;
                selectedRadio = R.id.red;
                break;
            case R.id.blue:
                color = android.R.color.holo_blue_dark;
                selectedRadio = R.id.blue;
                break;
            case R.id.green:
                color = android.R.color.holo_green_dark;
                selectedRadio = R.id.green;
                break;
            case R.id.black:
                color = android.R.color.black;
                selectedRadio = R.id.black;
                break;
        }

        SharedPreferences.Editor editor = sharedPref.edit();
        editor.putInt(getString(R.string.widget_color), color);
        editor.putInt(getString(R.string.widget_color_button), selectedRadio);
        editor.commit();

        applySettings();
    }

    //apply selected settings to the widget to see how widget
    //looks with the selected settings.
    private void applySettings() {

        int widgetBackground = sharedPref.getInt(getString(R.string.widget_background), 0);
        //0 means transparent , 255 opaque
        //convert value to percentage
        int transparentBackground = widgetBackground * 100 / 255;

        int textcolor = sharedPref.getInt(getString(R.string.widget_color), android.R.color.black);

        container.getBackground().setAlpha(transparentBackground);
        loactionName.setTextColor(ContextCompat.getColor(getApplicationContext(), textcolor));
    }

    //close settings screen
    public void closeSettings(View v) {
        //update all widgets to apply the slected settings.
        LocationAppWidgetProvider.updateLocationWidget(getApplicationContext(), null);

        Intent widgetIntent = new Intent();
        setResult(RESULT_OK, widgetIntent);
        finish();
        //System.exit(0);
    }
}

Widget Configuration Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_margin="8dp"
    android:orientation="vertical"
    tools:context=".WidgetConfigurationActivity">
    <TextView
        android:id="@+id/widgetTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:text="Location Widget"
        android:textAppearance="@style/TextAppearance.AppCompat.Large" />

    <include layout="@layout/location_widget_layout" />

    <android.support.constraint.ConstraintLayout
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="8dp">


        <TextView
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="32dp"
            android:text="Location Widget Settings"
            android:textAppearance="@style/TextAppearance.AppCompat.Large"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <RadioGroup
            android:id="@+id/widgetColor"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:orientation="vertical"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv">

            <TextView
                android:id="@+id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="8dp"
                android:text="Color"
                android:textAppearance="@style/TextAppearance.AppCompat.Large" />

            <RadioButton
                android:id="@+id/red"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="onChooseColor"
                android:text="Red" />

            <RadioButton
                android:id="@+id/blue"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="onChooseColor"
                android:text="Blue" />

            <RadioButton
                android:id="@+id/green"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="onChooseColor"
                android:text="Green" />

            <RadioButton
                android:id="@+id/black"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="onChooseColor"
                android:text="Black" />
        </RadioGroup>

        <TextView
            android:id="@+id/title2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:text="Background"
            android:textAppearance="@style/TextAppearance.AppCompat.Large"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/widgetColor" />

        <SeekBar
            android:id="@+id/widgetBackground"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:max="255"
            android:min="0"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/title2" />

        <Button
            android:id="@+id/close_settings"
            style="@style/Widget.AppCompat.Button.Colored"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="closeSettings"
            android:text="Close Settings"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/widgetBackground" />

    </android.support.constraint.ConstraintLayout>
</LinearLayout>

Widget Background Opacity Setting

User is presented with SeekBar in the widget configuration screen for selecting opacity or transparency value from 0 to 255 for widget background. User selected opacity values will be used to create widget background color using argb() function of Color. Thus created transparent background color will be applied to widget background using setInt method on remote views object passing container element id, method name setBackgroundColor and color arguments to it.

int widgetBackground = 255 - sharedPref.getInt(
        context.getString(R.string.widget_background), 255);
Color bg = Color.valueOf(context.getResources().getColor(R.color.widgetBg, null));
widgetBackground = Color.argb((float)widgetBackground, bg.red(), bg.green(), bg.blue() );        

rviews.setInt(R.id.container, "setBackgroundColor", widgetBackground);

Widget Text Color Setting

Text color is applied to text views of widget using setTextColor method of RemoteViews object passing text view id and color arguments to it.

int textcolor = sharedPref.getInt(context.getString(R.string.widget_color), R.id.black);
rviews.setTextColor(R.id.location_name, ContextCompat.getColor(context, textcolor));

Widget Provider XML

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="100dp"
    android:minHeight="80dp"
    android:updatePeriodMillis="86400000"
    android:previewImage="@drawable/location"
    android:initialLayout="@layout/location_widget_layout"
    android:resizeMode="horizontal|vertical"
    android:configure="com.zoftino.locationinfowidget.WidgetConfigurationActivity"
    android:widgetCategory="home_screen">
</appwidget-provider>
android widget example

Android Manifest XML

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zoftino.locationinfowidget">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        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>
        <activity android:name=".WidgetConfigurationActivity">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".LocationAppWidgetProvider"
            android:enabled="true">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                <action android:name="com.zoftino.locationinfowidget.DISPLAY_PLACE" />
                <action android:name="com.zoftino.locationinfowidget.WIDGET_CLICK" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/location_widget" />
        </receiver>
    </application>
    
</manifest>