1

While developing my note-taking app and as a new feature I wanna create a widget for my app by jetpack lib glance.

At first, I thought it would be like a normal database implementation, but it didn't work, And I have no idea how it does, even when I made some searches I get only network and API stuff.

But in my case, I wanted the local data.

And here are my classes.

@HiltViewModel
class AppWidgetVM @Inject constructor(
    private val repo: EntityRepoImp
): ViewModel() {
    private val _allNotesById = MutableStateFlow<List<Entity>>(emptyList())
    val allNotesById: StateFlow<List<Entity>>
        get() = _allNotesById.stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(),
            initialValue = listOf()
        )
    init {
        viewModelScope.launch(Dispatchers.IO) {
            repo.getAllNotesById.collect {
                _allNotesById.value = it
            }
        }
    }
}
@AndroidEntryPoint
class AppWidgetReceiver: GlanceAppWidgetReceiver() {
    override val glanceAppWidget: GlanceAppWidget = AppWidget()
}
class AppWidget: GlanceAppWidget() {

    @Composable
    override fun Content() {

      ...

    }
}

I know its looks like empty code, But like I said I have no idea how it does.

The variable allNotesById holds all my local notes, And that is exactly what I want to display in AppWidget class.

Youn Tivoli
  • 230
  • 2
  • 13

2 Answers2

2

As of glance 1.0.0-alpha05, I couldn't access my ViewModel from my GlanceAppWidget(), I guess there's no support for that yet. I had this same issue and my work around was to extract the list from my database and store in it Preferences as json, then deserialise and use it inside content() composable, inside the receiver class. so for you it'd look something like this:

@AndroidEntryPoint
class AppWidgetReceiver: GlanceAppWidgetReceiver() {
    override val glanceAppWidget: GlanceAppWidget = AppWidget()

    private val coroutineScope = MainScope()

    @Inject
    lateinit var repo: EntityRepoImp

    private fun observeData(context: Context) {

        coroutineScope.launch {

            val allNotes = repo.getAllNotesById

            val serializedList = Gson().toJson(allNotes)

            val glanceId = GlanceAppWidgetManager(context).getGlanceIds(AppWidget::class.java).firstOrNull()

            if (glanceId != null) {
                updateAppWidgetState(context, PreferencesGlanceStateDefinition, glanceId) { prefs ->
                    prefs.toMutablePreferences().apply {
                        this[currentTimes] = serializedList
                    }
                }
                glanceAppWidget.update(context, glanceId)
            }
        }
    }

    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray
    ) {
        super.onUpdate(context, appWidgetManager, appWidgetIds)
        observeData(context)
    }

    override fun onReceive(context: Context, intent: Intent) {
        super.onReceive(context, intent)
        observeData(context)
    }

    companion object {
        val allNotesKey = stringPreferencesKey("all_notes")
    }

}

Don't forget to call observeData(context) in onUpdate and onReceive methods of your reciever..

class AppWidget: GlanceAppWidget() {

    override val stateDefinition: GlanceStateDefinition<*> = PreferencesGlanceStateDefinition

    @Composable
    override fun Content() {
        val context = LocalContext.current
        val prefs = currentState<Preferences>()
        val deserializedList = prefs[AppWidgetReceiver.allNotesKey] ?: ""
        val list = Gson().fromJson<List< Entity>>(deserializedList, object : TypeToken<List< Entity>>() {}.type)

        LazyColumn(
                modifier = GlanceModifier
            ) {
                items(
                    items = list
                ) { list ->
                    ...
                }
            }

    }
}
EA.
  • 21
  • 4
  • Thank you "EA." for your response it looks very nice solution, But in fact, I find something else after a long search in the public similar apps for my app, And luckily I find [favorite-apps-widget](https://github.com/chrimaeon/favorite-apps-widget) project, I just forget to put it here. – Youn Tivoli Apr 20 '23 at 21:53
  • How to update the value in the widget it changes – Tippu Fisal Sheriff May 16 '23 at 21:37
1

So after a long search in public similar apps for my app and depending on favorite-apps-widget project. I finally ended with this solution:

GlanceAppWidget

class AppWidget:GlanceAppWidget() {

    @Composable
    override fun Content() {

        val ctx = LocalContext.current.applicationContext

        val viewModel = EntryPoints.get(
            ctx,
            EntryPoint::class.java
        ).vm()

        WidgetListNotes(ctx = ctx, widgetVm = viewModel)
    }

    @Composable
    fun WidgetListNotes(
        ctx: Context,
        widgetVm: WidgetVM
    ) {
         ...
    }
}

EntryPoint

@EntryPoint
@InstallIn(SingletonComponent::class)
interface EntryPoint {

    fun vm(): WidgetVM
}

GlanceAppWidgetReceiver

@AndroidEntryPoint
class WidgetReceiver:GlanceAppWidgetReceiver() {
    override val glanceAppWidget: GlanceAppWidget
        get() = AppWidget()

     override fun onReceive(context: Context, intent: Intent) {
        ...
     }

WidgetViewModel

class WidgetVM @Inject constructor(
    private val widgetEntityRepoImpl: WidgetEntityRepoImpl
) {

    @WorkerThread
    fun getAllEntities() = widgetEntityRepoImpl.getAllWidgetEntityById

    ...
}

In MainActivity I decided to update the widget data every pause in the application, you can do it in another place.

...
    override fun onPause() {
        super.onPause()
        WidgetReceiver.updateBroadcast(this)
    }
...

Screenshot.

enter image description here

For the rest of the code check my project JetNote.

Youn Tivoli
  • 230
  • 2
  • 13