This widget is a joke

<?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"
android:gravity="center"
android:padding="16dp">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="20sp"
android:layout_marginBottom="8dp"
android:text="@string/configure" />
<TableRow
android:layout_height="40dp"
android:layout_width="match_parent"
android:layout_gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/btDarkMode"
android:theme="@style/Theme.AppCompat"
android:background="@drawable/rs_dark"
android:layout_height="match_parent"
android:text="@string/dark"
android:textColor="@color/white"
android:gravity="center"
android:layout_weight="1"
android:layout_margin="2dp"/>
<Button
android:id="@+id/btLightMode"
android:theme="@style/Theme.AppCompat"
android:background="@drawable/rs_light"
android:layout_height="match_parent"
android:text="@string/light"
android:textColor="@color/black"
android:layout_weight="1"
android:gravity="center"
android:layout_margin="2dp"/>
</TableRow>

<Button
android:id="@+id/btAddWidget"
android:theme="@style/Theme.AppCompat.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/black"
android:textSize="20sp"
android:layout_marginTop="8dp"
android:text="@string/add_widget" />
</LinearLayout>
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#311331" />
<stroke
android:width="1dp"
android:color="#20FFFFFF" />
<corners android:radius="20dp" />
<padding
android:bottom="5dp"
android:left="5dp"
android:right="5dp"
android:top="5dp" />
</shape>
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#60DF59F3" />
<stroke
android:width="1dp"
android:color="#20FFFFFF" />
<corners android:radius="20dp" />
<padding
android:bottom="5dp"
android:left="5dp"
android:right="5dp"
android:top="5dp" />
</shape>
package com.my.jokeswidget;

import android.app.Activity;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import androidx.core.content.ContextCompat;

public class MyWidgetConfigureActivity extends Activity {

private static final String PREFS_NAME = "com.my.jokeswidget.MyWidget";
private static final String PREF_PREFIX_KEY = "appwidget_";

int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
Button btDarkMode, btLightMode, btAddWidget;
int widgetMode = R.drawable.rs_dark;

View.OnClickListener darkClickListener = view -> {
widgetMode = R.drawable.rs_dark;
btDarkMode.setForeground(new ColorDrawable(ContextCompat.getColor(getBaseContext(), R.color.trans_yellow)));
btLightMode.setForeground(new ColorDrawable(Color.TRANSPARENT));
};

View.OnClickListener lightClickListener = view -> {
widgetMode = R.drawable.rs_light;
btLightMode.setForeground(new ColorDrawable(ContextCompat.getColor(getBaseContext(), R.color.trans_yellow)));
btDarkMode.setForeground(new ColorDrawable(Color.TRANSPARENT));
};

View.OnClickListener addWidgetClickListener = v -> {
final Context context = MyWidgetConfigureActivity.this;

// When the button is clicked, store the background locally
saveTitlePref(context, mAppWidgetId, widgetMode);

// It is the responsibility of the configuration activity to update the app widget
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
MyWidget.updateAppWidget(context, appWidgetManager, mAppWidgetId);

// Make sure we pass back the original appWidgetId
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
};

public MyWidgetConfigureActivity() {
super();
}

// Write the prefix to the SharedPreferences object for this widget
static void saveTitlePref(Context context, int appWidgetId, int modeId) {
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.putInt(PREF_PREFIX_KEY + appWidgetId, modeId);
prefs.apply();
}


static Integer loadModePref(Context context, int appWidgetId) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
int modeId = prefs.getInt(PREF_PREFIX_KEY + appWidgetId, 0);
if (modeId == 0) {
return R.drawable.rs_dark;
} else {
return modeId;
}
}

static void deleteModePref(Context context, int appWidgetId) {
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.remove(PREF_PREFIX_KEY + appWidgetId);
prefs.apply();
}

@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setResult(RESULT_CANCELED);
setContentView(R.layout.my_widget_configure);

btDarkMode = findViewById(R.id.btDarkMode);
btLightMode = findViewById(R.id.btLightMode);
btAddWidget = findViewById(R.id.btAddWidget);

btDarkMode.setOnClickListener(darkClickListener);
btLightMode.setOnClickListener(lightClickListener);
btAddWidget.setOnClickListener(addWidgetClickListener);

// Find the widget id from the intent.
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
}

// If this activity was started with an intent without an app widget ID, finish with an error.
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
}
}
}
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {

int modeId = MyWidgetConfigureActivity.loadModePref(context, appWidgetId);
CharSequence loadingText = context.getString(R.string.loading);

// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_widget);
views.setTextViewText(R.id.appwidget_text, loadingText);
views.setInt(R.id.rlWidget, "setBackgroundResource", modeId);

// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/rs_dark"
android:padding="@dimen/widget_margin"
android:id="@+id/rlWidget"
android:orientation="vertical"
android:gravity="center"
android:theme="@style/ThemeOverlay.JokesWidget.AppWidgetContainer">

<TextView
android:id="@+id/tvJokeSet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:lines="2"
android:gravity="center"
android:text="@string/loading"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold|italic" />
<TextView
android:id="@+id/tvJokePunch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:lines="2"
android:gravity="center"
android:text="@string/click_for_punchline"
android:textColor="@color/light_blue_900"
android:textSize="16sp"
android:textStyle="bold|italic" />
<TextView
android:theme="@style/Theme.AppCompat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:padding="6dp"
android:background="@drawable/round_frame"
android:textSize="14sp"
android:text="@string/random_joke"
/>
</LinearLayout>
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="1dp"
android:color="@color/white" />
<corners android:radius="10dp" />
<padding
android:bottom="5dp"
android:left="5dp"
android:right="5dp"
android:top="5dp" />
</shape>
package com.my.jokeswidget;

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.util.Pair;
import android.widget.RemoteViews;

import java.util.Random;

public class MyWidget extends AppWidgetProvider {

private static final String RANDOM_JOKE_CLICKED = "widgetRandomJokeClick";
private static final String PUNCHLINE_CLICKED = "widgetJokePunchlineClick";
private static Pair<String, String> randJoke;

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

// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_widget);
CharSequence loadingText = context.getString(R.string.loading);
views.setTextViewText(R.id.tvJokeSet, loadingText);

// Widget drawable background by user selection
int modeId = MyWidgetConfigureActivity.loadModePref(context, appWidgetId);
views.setInt(R.id.rlWidget, "setBackgroundResource", modeId);

// Setup new joke
randJoke = getNewJoke();
views.setTextViewText(R.id.tvJokeSet, randJoke.first);

// Click intents
views.setOnClickPendingIntent(R.id.tvRandomJoke,
getPendingSelfIntent(context, RANDOM_JOKE_CLICKED));
views.setOnClickPendingIntent(R.id.tvJokePunch,
getPendingSelfIntent(context, PUNCHLINE_CLICKED));

// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}

protected static PendingIntent getPendingSelfIntent(Context context, String action) {
Intent intent = new Intent(context, MyWidget.class);
intent.setAction(action);
return PendingIntent.getBroadcast(context, 0, intent, 0);
}

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

if (RANDOM_JOKE_CLICKED.equals(intent.getAction())) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_widget);
ComponentName jokeWidget = new ComponentName(context, MyWidget.class);
randJoke = getNewJoke();
remoteViews.setTextViewText(R.id.tvJokeSet, randJoke.first);
remoteViews.setTextViewText(R.id.tvJokePunch, context.getString(R.string.click_for_punchline));
appWidgetManager.updateAppWidget(jokeWidget, remoteViews);
}

if (PUNCHLINE_CLICKED.equals(intent.getAction())) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_widget);
ComponentName jokeWidget = new ComponentName(context, MyWidget.class);
remoteViews.setTextViewText(R.id.tvJokePunch, randJoke.second);
appWidgetManager.updateAppWidget(jokeWidget, remoteViews);
}
}

private static Pair<String, String> getNewJoke(){
int rand = new Random().nextInt(100);
return new Pair<>("Random joke number: " + rand,
"Punchline number: " + rand);
}

@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 onDeleted(Context context, int[] appWidgetIds) {
// When the user deletes the widget, delete the preference associated with it.
for (int appWidgetId : appWidgetIds) {
MyWidgetConfigureActivity.deleteModePref(context, appWidgetId);
}
}

@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}

@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
}
}
<activity android:name=".MyWidgetConfigureActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
dependencies {
...
implementation 'com.android.volley:volley:1.2.0'
...
}
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
package com.my.jokeswidget.httpclient;

import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
import android.content.Context;

public class VolleyRQSingleton {
private static VolleyRQSingleton mInstance;
private RequestQueue mRequestQueue;
private static Context mCtx;

private VolleyRQSingleton(Context context) {
mCtx = context;
mRequestQueue = getRequestQueue();
}

public static synchronized VolleyRQSingleton getInstance(Context context) {
if (mInstance == null) {
mInstance = new VolleyRQSingleton(context);
}
return mInstance;
}

public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {

mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
}
return mRequestQueue;
}

public <T> void addToRequestQueue(Request<T> req) {
req.setRetryPolicy(new DefaultRetryPolicy(
5000,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

getRequestQueue().add(req);
}
}
package com.my.jokeswidget.httpclient;

public interface VolleyCallback {

void onSuccess(Boolean isSuccess);
}
package com.my.jokeswidget.httpclient;

import android.content.Context;
import android.util.Pair;

import com.android.volley.Request;
import com.android.volley.toolbox.JsonObjectRequest;

import org.json.JSONException;

public class JokesClient {

private Pair<String, String> joke;

public void fetchJokeFromServer(Context context, final VolleyCallback callback) {
String url = "https://official-joke-api.appspot.com/jokes/random";
JsonObjectRequest jsonRequest = new JsonObjectRequest(Request.Method.GET, url, null,
response -> {
try {
String setup = (response.getString("setup"));
String punchline = (response.getString("punchline"));
joke = new Pair<>(setup, punchline);
} catch (JSONException e) {
e.printStackTrace();
}
callback.onSuccess(true);
},
error -> {
System.out.println(error);
callback.onSuccess(false);
});

VolleyRQSingleton.getInstance(context).addToRequestQueue(jsonRequest);
}

public Pair<String, String> joke(){
return joke;
}
}
package com.my.jokeswidget;

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.util.Pair;
import android.widget.RemoteViews;

import com.my.jokeswidget.httpclient.JokesClient;


public class MyWidget extends AppWidgetProvider {

private static final String RANDOM_JOKE_CLICKED = "widgetRandomJokeClick";
private static final String PUNCHLINE_CLICKED = "widgetJokePunchlineClick";
private static Pair<String, String> randJoke;
private static final JokesClient jokesClient = new JokesClient();

static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_widget);
CharSequence loadingText = context.getString(R.string.loading);
views.setTextViewText(R.id.tvJokeSet, loadingText);

// Widget drawable background by user selection
int modeId = MyWidgetConfigureActivity.loadModePref(context, appWidgetId);
views.setInt(R.id.rlWidget, "setBackgroundResource", modeId);

// Click intents
views.setOnClickPendingIntent(R.id.tvRandomJoke,
getPendingSelfIntent(context, RANDOM_JOKE_CLICKED));
views.setOnClickPendingIntent(R.id.tvJokePunch,
getPendingSelfIntent(context, PUNCHLINE_CLICKED));

// Set a new random joke
ComponentName jokeWidget = new ComponentName(context, MyWidget.class);
setRandomJoke(views, context, appWidgetManager, jokeWidget);
}

protected static PendingIntent getPendingSelfIntent(Context context, String action) {
Intent intent = new Intent(context, MyWidget.class);
intent.setAction(action);
return PendingIntent.getBroadcast(context, 0, intent, 0);
}

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

if (RANDOM_JOKE_CLICKED.equals(intent.getAction())) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_widget);
ComponentName jokeWidget = new ComponentName(context, MyWidget.class);
setRandomJoke(remoteViews, context, appWidgetManager, jokeWidget);
} else if (PUNCHLINE_CLICKED.equals(intent.getAction())) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_widget);
ComponentName jokeWidget = new ComponentName(context, MyWidget.class);
remoteViews.setTextViewText(R.id.tvJokePunch, randJoke.second);
appWidgetManager.updateAppWidget(jokeWidget, remoteViews);
}
}

private static void setRandomJoke(RemoteViews remoteViews, Context context,
AppWidgetManager appWidgetManager, ComponentName jokeWidget){

remoteViews.setTextViewText(R.id.tvJokeSet, context.getString(R.string.loading));
remoteViews.setTextViewText(R.id.tvJokePunch, context.getString(R.string.click_for_punchline));
appWidgetManager.updateAppWidget(jokeWidget, remoteViews);

jokesClient.fetchJokeFromServer(context, isSuccess -> {
if (!isSuccess){
randJoke = new Pair<>(context.getString(R.string.loading_failed), "");
} else {
randJoke = jokesClient.joke();
}
remoteViews.setTextViewText(R.id.tvJokeSet, randJoke.first);
appWidgetManager.updateAppWidget(jokeWidget, remoteViews);
});
}

@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 onDeleted(Context context, int[] appWidgetIds) {
// When the user deletes the widget, delete the preference associated with it.
for (int appWidgetId : appWidgetIds) {
MyWidgetConfigureActivity.deleteModePref(context, appWidgetId);
}
}

@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}

@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
}
}
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:configure="com.my.jokeswidget.MyWidgetConfigureActivity"
android:initialKeyguardLayout="@layout/my_widget"
android:initialLayout="@layout/my_widget"
android:minWidth="250dp"
android:minHeight="80dp"
android:previewImage="@drawable/jokes_img"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen|keyguard"/>

Conclusion

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store