Glance???

Today, as usual, I went to Google’s official documentation to check the latest dependency updates. I found that Google updated a batch of dependencies yesterday:

It doesn’t look unusual, it’s just a bunch of dependent updates, but as I scroll down…

I found the library marked by the arrow above, it is still very new, version 1.0.0, and it is the first alpha release, this is a new thing, the name is familiar! I remember that God Guo Lin once wrote a library also called Glance:

I thought it was guo God’s library was officially included, the results into a look is not…

I was thrilled to see the library’s introduction, which basically says: you can use the Compose style API to build layouts for widgets.

I wrote the other day: Don’t envy Apple widgets, Android has them too!

Here are some updates to Android S widgets and some bugs with them.

One of the problems with widgets is that you can only use RemotesView to write layouts. Now that you see the library, it feels like the problem has been solved!!

So immediately open the weather app I wrote before, to test it!

The code in this article addresses: play Github:https://github.com/zhujiang521/PlayWeather weather

Began to lu code

Add the dependent

The first step to using a library is to add a dependency. Take a look at the Glance dependency:

Dependencies {implementation "androidx glance: glance: 1.0.0 - alpha01"} android {buildFeatures {compose true} ComposeOptions kotlinOptions {kotlinCompilerExtensionVersion = "1.1.0 - rc01"} {jvmTarget = "1.8"}}Copy the code

Add dependencies to your project. For example, if you have Compose in your project, add the following dependencies to your project.

Creating the widget

As you all know, the widget is actually a BroadcastReceiver, so we need to configure it in the manifest:

<receiver android:name=".common.widget.glance.FirstGlanceWidgetReceiver" android:enabled="@bool/glance_appwidget_available" android:exported="false"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <! - the widget configuration information - > < meta - data android: name = "android. Appwidget. The provider" android: resource = "@ XML/first_glance_widget_info" / > </receiver>Copy the code

Glance_appwidget_available glance_appwidgeT_available glance_appwidget_available Glance_appwidget_available glance_appwidget_available glance_appwidgeT_available

Glance_appwidget_available glance_appwidget_available glance_appwidget_available glance_appwidget_available glance_appwidget_available glance_appwidget_available glance_appwidget_available glance_appwidget_available glance_appwidget_available

Let’s write down the resource first_glance_widget_info:

<? The XML version = "1.0" encoding = "utf-8"? > <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="180dp" android:minHeight="50dp" android:previewImage="@mipmap/today_preview" android:resizeMode="horizontal|vertical" android:targetCellWidth="3" android:targetCellHeight="1" android:widgetCategory="home_screen" />Copy the code

A few configurations are simply set: minimum width, preview image, and default width and height.

AppWidgetProvider

AppWidgetProvider, GlanceAppWidgetReceiver, GlanceAppWidgetReceiver, GlanceAppWidgetReceiver, GlanceAppWidgetReceiver GlanceAppWidgetReceiver GlanceAppWidgetReceiver GlanceAppWidgetReceiver

abstract class GlanceAppWidgetReceiver : AppWidgetProvider() { private companion object { private const val TAG = "GlanceAppWidgetReceiver" } /** * GlanceAppWidget instance used to generate an application Widget and send it to AppWidgetManager */ Abstract Val GlanceAppWidget: GlanceAppWidget @callsuper Override fun onUpdate(context: context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray ) { goAsync { updateManager(context) appWidgetIds.map { async { glanceAppWidget.update(context, AppWidgetManager, it)}}. AwaitAll ()}} / / layout change when refresh the widget @ CallSuper override fun onAppWidgetOptionsChanged (context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle ) { goAsync { updateManager(context) glanceAppWidget.resize(context, appWidgetManager, appWidgetId, newOptions) } } @CallSuper override fun onDeleted(context: Context, appWidgetIds: IntArray) { goAsync { updateManager(context) appWidgetIds.forEach { glanceAppWidget.deleted(context, It)}}} / / update the widget private fun CoroutineScope. UpdateManager (context: Context) { launch { runAndLogExceptions { GlanceAppWidgetManager(context) .updateReceiver(this@GlanceAppWidgetReceiver, glanceAppWidget) } } } override fun onReceive(context: Context, intent: Intent) { runAndLogExceptions { if (intent.action == Intent.ACTION_LOCALE_CHANGED) { val appWidgetManager = AppWidgetManager.getInstance(context) val componentName = ComponentName(context.packageName, checkNotNull(javaClass.canonicalName)) onUpdate( context, appWidgetManager, appWidgetManager.getAppWidgetIds(componentName) ) return } super.onReceive(context, intent) } } }Copy the code

The GlanceAppWidgetReceiver code above has been truncated. GlanceAppWidgetReceiver GlanceAppWidgetReceiver GlanceAppWidgetReceiver GlanceAppWidgetReceiver GlanceAppWidgetReceiver is an abstract class. There is an instance of glanceAppWidget that needs to be subclassed to build. GlanceAppWidget GlanceAppWidget is also an abstract class. This article will focus on the use of GlanceAppWidget because there is too much code in the class.

Use Glance to write the layout

Here’s how to use it:

class FirstGlanceWidgetReceiver : GlanceAppWidgetReceiver() {
​
    override val glanceAppWidget: GlanceAppWidget = FirstGlanceWidget()
​
}
Copy the code

Using GlanceAppWidgetReceiver is as simple as creating a class that inherits GlanceAppWidgetReceiver and instantiates glanceAppWidget.

Here’s how to initialize a GlanceAppWidget:

class FirstGlanceWidget : GlanceAppWidget() { @Composable override fun Content() { Column(modifier = GlanceModifier .fillMaxSize() .background(day  = Color.Red, night = Color.Blue) .cornerRadius(10.dp) .padding(8.dp) ) { Text( text = "First Glance widget", modifier = GlanceModifier.fillMaxWidth(), style = TextStyle(fontWeight = FontWeight.Bold), ) } } }Copy the code

Come on, let’s start looking for something different! Compose Compose from Glance

That’s right! Modifier is not the same, now Modifier into GlanceModifier, in fact, more than the Modifier changed, we look at other dependence:

import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.glance.Button
import androidx.glance.GlanceModifier
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.action.actionStartActivity
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.background
import androidx.glance.appwidget.cornerRadius
import androidx.glance.appwidget.lazy.LazyColumn
import androidx.glance.layout.*
import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
Copy the code

I was confused when I wrote it… In this case, Glance overwrote the Compose layout.

Remember from the previous article that you could only draw simple layouts in widgets. I thought Glance would change that, but…

Composable items in Glance

It is not much different from the previous widget, and can only support the following composable items:

Box, Row, Column, Text, Button, LazyColumn, Image, Spacer

Use them all:

@Composable override fun Content() { Column( modifier = GlanceModifier .fillMaxSize() .background(day = Color.Red, night = Color.Blue) .cornerRadius(10.dp) .padding(8.dp) ) { Text( text = "First Glance widget", modifier = GlanceModifier.fillMaxWidth(), style = TextStyle(fontWeight = FontWeight.Bold), ) Spacer(modifier = GlanceModifier.height(5.dp)) Row { LazyColumn(modifier = GlanceModifier.width(150.dp)) { items(4) { Image(provider = ImageProvider(r.map.back_100d), modifier = GlanceModifier. Height (50.dp), ContentDescription = "")} Row {Text(Text = "1") Text(Text =" 2 ") Modifier = glancemodifier.padding (10.dp))} Button(modifier = glancemodifier.padding (10.dp)) onClick = actionStartActivity(MainActivity::class.java)) } }Copy the code

This basically writes down all of the composable items supported above. Now that the widget is basically written, let’s run it and see what it looks like:

Basically the desired effect. To recap, Glance has rewritten all the composable items, most of which are the same as before, but there are some differences.

Image

For example, ‘Compose’, ‘Compose’ :

@Composable fun Image( painter: Painter, contentDescription: String? , modifier: Modifier = Modifier, alignment: Alignment = Alignment.Center, contentScale: ContentScale = ContentScale.Fit, alpha: Float = DefaultAlpha, colorFilter: ColorFilter? = null )Copy the code

The above is the method definition of the Image composable item in Compose. The Image resource is “Painter”.

Image( modifier = modifier, painter = BitmapPainter(bitmap), contentDescription = "", ContentScale = contentScale) Image(Modifier = modifier, Painter = painterResource(resource id), contentDescription = "", contentScale = contentScale )Copy the code

But take a look at the Image from Glance:

@Composable public fun Image( provider: ImageProvider, contentDescription: String? , modifier: GlanceModifier = GlanceModifier, contentScale: ContentScale = ContentScale.Fit )Copy the code

Do you see that? ImageProvider is used as an ImageProvider, which is used as an ImageProvider.

Public Fun ImageProvider(@drawableres resId: Int): ImageProvider = AndroidResourceImageProvider (resId) / / bitmap loaded public fun ImageProvider (bitmap: bitmap) : ImageProvider = BitmapImageProvider(bitmap) @requiresAPI (build.version_codes.m) public Fun ImageProvider(Icon:  Icon): ImageProvider = IconImageProvider(icon)Copy the code

As you can see, the ImageProvider has three overloaded methods that can be instantiated, basically meeting the requirements of loading an Image.

Button

The code above shows that the Button is also different from the previous one. Let’s take a look at the previous one:

@Composable
fun Button(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    elevation: ButtonElevation? = ButtonDefaults.elevation(),
    shape: Shape = MaterialTheme.shapes.small,
    border: BorderStroke? = null,
    colors: ButtonColors = ButtonDefaults.buttonColors(),
    contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
    content: @Composable RowScope.() -> Unit
)
Copy the code

“Compose” is composed’s Button, which is familiar to everyone. Take a look at the “Glance” Button:

@Composable
fun Button(
    text: String,
    onClick: Action,
    modifier: GlanceModifier = GlanceModifier,
    enabled: Boolean = true,
    style: TextStyle? = null,
    maxLines: Int = Int.MAX_VALUE,
) 
Copy the code

It’s time to find different times again. What’s the difference between these two buttons?

OnClick is a callback event, but now it’s an Action.

Why use Glance

I’ve been thinking about this while using this library, why am I using this library? To import more packages? Just to make it a little bit more complicated, right? For the project to be harder to maintain later (consider co-maintenance)?

More convenient actions

PendingIntent is an intent that we used to use with widgets, but now Glance doesn’t need it. Instead, it uses a more convenient Action.

ActionStart theActivity

Let’s start the Activity using Action:

Public fun actionStartActivity(componentName: componentName, parameters: ActionParameters = actionParametersOf() ): Action = StartActivityComponentAction (the componentName, parameters) / / the name of the class start public fun < T: Activity> actionStartActivity( activity: Class<T>, parameters: ActionParameters = actionParametersOf() ): ActionStartActivity public Inline fun <reified T: Action = StartActivityClassAction(activity, parameters) Activity> actionStartActivity( parameters: ActionParameters = actionParametersOf() ): Action = actionStartActivity(T::class.java, parameters)Copy the code

As you can see, there are several ways to write. Let’s look at calling methods:

Button(text = "Glance ", onClick = actionStartActivity(" ComponentName "," ComponentName ")) Button(text = "Glance ", OnClick = actionStartActivity<MainActivity>() Button(text = "Glance Button ", onClick = actionStartActivity(MainActivity::class.java))Copy the code

ActionExecute the callback task

Take a look at the source code:

public fun <T : ActionCallback> actionRunCallback(
    callbackClass: Class<T>,
    parameters: ActionParameters = actionParametersOf()
): Action = RunCallbackAction(callbackClass, parameters)
​
public inline fun <reified T : ActionCallback> actionRunCallback(
    parameters: ActionParameters = actionParametersOf()
): Action = actionRunCallback(T::class.java, parameters)
Copy the code

The code is simple, so let’s see how it works.

First you need to create a class and implement ActionCallback:

class ActionCallbacks:ActionCallback{ override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) {// Do what you want}}Copy the code

The normal call can then be made:

Button(text = "Glance ", onClick = actionRunCallback<ActionCallbacks>()) onClick = actionRunCallback(ActionCallbacks::class.java))Copy the code

ActionStart theService

Also, take a look at the source code:

public fun actionStartService(intent: Intent, isForegroundService: Boolean = false): Action =
    StartServiceIntentAction(intent, isForegroundService)
​
public fun actionStartService(
    componentName: ComponentName,
    isForegroundService: Boolean = false
): Action = StartServiceComponentAction(componentName, isForegroundService)
​
public fun <T : Service> actionStartService(
    service: Class<T>,
    isForegroundService: Boolean = false
): Action =
    StartServiceClassAction(service, isForegroundService)
​
public inline fun <reified T : Service> actionStartService(
    isForegroundService: Boolean = false
): Action = actionStartService(T::class.java, isForegroundService)
Copy the code

We need to create a Service as well:

class TestService : Service() { override fun onBind(intent: Intent?) : IBinder? { TODO("Not yet implemented") } }Copy the code

Here’s how to use it:

OnClick = actionStartService<TestService>(); onClick = actionStartService<TestService>(); onClick = actionStartService(TestService::class.java))Copy the code

ActionStart the radio

public fun actionStartBroadcastReceiver(
    action: String,
    componentName: ComponentName? = null
): Action = StartBroadcastReceiverActionAction(action, componentName)
​
public fun actionStartBroadcastReceiver(intent: Intent): Action =
    StartBroadcastReceiverIntentAction(intent)
​
public fun actionStartBroadcastReceiver(componentName: ComponentName): Action =
    StartBroadcastReceiverComponentAction(componentName)
​
public fun <T : BroadcastReceiver> actionStartBroadcastReceiver(receiver: Class<T>): Action = StartBroadcastReceiverClassAction(receiver)
​
public inline fun <reified T : BroadcastReceiver> actionStartBroadcastReceiver(): Action = actionStartBroadcastReceiver(T::class.java)
Copy the code

Starting a broadcast in a widget is usually sent to itself, so instead of recreating a broadcast, let’s see how it works:

Button(text = "Glance ", The onClick = actionStartBroadcastReceiver < FirstGlanceWidgetReceiver > Button ()) (text = "Glance Button", onClick = actionStartBroadcastReceiver(FirstGlanceWidgetReceiver::class.java))Copy the code

ActionParameters

ActionParameters have actually appeared many times above, the purpose of ActionParameters is to provide parameters for Action.

Take a look at the source code:

public class Key<T : Any> (public val name: String) {
    public infix fun to(value: T): Pair<T> = Pair(this, value)
}
​
public fun actionParametersOf(vararg pairs: ActionParameters.Pair<out Any>): ActionParameters =
    mutableActionParametersOf(*pairs)
Copy the code

Here’s how to use it:

val test = ActionParameters.Key<String>("test") val testInt = ActionParameters.Key<Int>("test") actionParametersOf(test To "test ", testInt to 123)Copy the code

Isn’t it simple and easy to get:

override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) {// Perform the required action val testString = requireNotNull(parameters[test]) val testInt = requireNotNull(parameters[testInt]) }Copy the code

More convenient LocalXXX

For those of you who have used Compose, you will know that the LocalContext in Compose is very convenient. You can use it anywhere, and you can use it in Glance, too. Take a look at the source code:

// Size of the generated overview view. Public val LocalSize = staticCompositionLocalOf<DpSize> {error("No default size")} // Context public Val LocalContext = staticCompositionLocalOf<Context> {error("No default Context ")} // Local view state, defined in the surface implementation. Customizable storage for viewing specific state data. public val LocalState = staticCompositionLocalOf<Any? Public val LocalGlanceId = staticCompositionLocalOf<GlanceId> {error("No default glance ID ")}Copy the code

The general idea is in the notes, so feel free to use it.

Simpler layout fit

Glance uses SizeMode to fit the layout. Look at the SizeMode source code:

Sealed Interface SizeMode {public object Single: SizeMode {public override Fun toString(): String = "sizemode.single"} // Provide a UI for each size that the App Widget may display. Public object Exact: SizeMode {public Override fun toString(): String = "sizemode. Exact"} // Provide UI for a Set of fixed sizes public class Responsive(val sizes: Set<DpSize>) : SizeMode}Copy the code

SizeMode is an interface. There are three classes that implement the SizeMode interface, with the meanings specified in the annotations. Here’s how to use it:

When (val localSizeMode = this.sizemode) {is sizemode. Single -> {// Single user interface} is sizemode. Exact -> {// Each possible size} is Sizemode. Responsive -> {// Provide UI for a group of fixed sizes}Copy the code

SizeMode has already been used in GlanceAppWidget source code. If you want to see how to use it in detail, you can check the source code of GlanceAppWidget.

A delicate ending

Now, Glance is based on Compose, and Google is doing a pretty good job with Compose. If you want to learn about Compose systematically, you can buy my new book, Jetpack Compose: Compose is the Compose framework for learning.

Purchase address of JINGdong

Dangdang Purchase address

Today’s code sample Github:https://github.com/zhujiang521/PlayWeather playing the weather

If it helps you, don’t forget to hit the Star. Thank you very much.

In fact, there are some details that I haven’t covered, so if you have any questions, please feel free to ask in the comments section.

The previous two days wrote the year-end summary, thought that was the last article of this year, did not expect Google again out of this library, can not help but share a wave with you, well, first write here, goodbye!