5

I'm trying to build a stackview widget, which should display differnt data when swiping through it. So far everything is working, but when I want to receive the index of the clicked card in the widget, I always get an extra or action of 'null'. I already have tried several workarounds, but none of them is working in my application. I also set up a complete new application for only making this work completely isolated. But here I get the exact same behaviour.

Several workarounds I tried to pass the index:

What I observed is, that the getViewAt funtion is called several times with a position(p0) of "0" before it gets called by the right positions from the generated exampleData List... But I don't know if this causes the Nulls in the onReceive function, when an card is clicked.

Here is my coding:

Manifest.xml

<service android:name=".ExampleWidgetService"
        android:permission="android.permission.BIND_REMOTEVIEWS">
</service>

WidgetLayout

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <StackView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/stackview">
    </StackView>

    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/empty_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textColor="#ffffff"
        android:textStyle="bold"
        android:text="empty"
        android:textSize="20sp" />
</FrameLayout>

WidgetItemEntry

<?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:layout_width="200dp"
        android:layout_height="200dp"
        android:text="Icon"
        android:id="@+id/newwidgetentry">

    </TextView>
</LinearLayout>

AppWidgetProvider

package com.example.widgettest
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
import android.widget.Toast

/**
 * Implementation of App Widget functionality.
 */
public const val clickedCategory: String = "com.example.myapplication.buttonpress.id"
public const val stackviewButton = "stackviewbutton"

class newwidget : AppWidgetProvider() {
    override fun onUpdate(
        context: Context,
        appWidgetManager: AppWidgetManager,
        appWidgetIds: IntArray,
    ) {
        // There may be multiple widgets active, so update all of them
        for (appWidgetId in appWidgetIds) {

        /*    val widgetText = context.getString(R.string.appwidget_text)
            // Construct the RemoteViews object
            val views = RemoteViews(context.packageName, R.layout.newwidget)
            views.setTextViewText(R.id.newwidgetentry, widgetText)*/

            val serviceIntent: Intent = Intent(context, ExampleWidgetService::class.java).apply{
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
            }
            val views = RemoteViews(
                context.packageName,
                R.layout.newwidget
            ).apply{
                setRemoteAdapter(R.id.stackview, serviceIntent)
                setEmptyView(R.id.stackview, R.id.empty_view)
            }

            val toastIntent = Intent(context, newwidget::class.java).run {
                action= stackviewButton
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
                PendingIntent.getBroadcast(context,
                    0,
                    this,
                    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
            }
            views.setPendingIntentTemplate(R.id.stackview, toastIntent)


            // Instruct the widget manager to update the widget
            appWidgetManager.updateAppWidget(appWidgetId, views)
        }
        super.onUpdate(context, appWidgetManager, appWidgetIds)
    }

    override fun onEnabled(context: Context) {
        // Enter relevant functionality for when the first widget is created
    }

    override fun onDisabled(context: Context) {
        // Enter relevant functionality for when the last widget is disabled
    }

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


   var clickedCat2 = intent?.getBundleExtra("test")?.getInt(clickedCategory,-1)
             /*val clickedCat2: Int? = intent?.getIntExtra(clickedCategory, -1)*/

        Toast.makeText(context, "onreceive +$clickedCat2" , Toast.LENGTH_SHORT).show();
        if (stackviewButton.equals(intent?.getAction())){
            val clickedCat: Int? = intent?.getIntExtra(clickedCategory, 0)
            Toast.makeText(context, "widgetbuttonpressed id + $clickedCat", Toast.LENGTH_SHORT).show();
        }
        super.onReceive(context, intent)
    }
}

RemoteView Factory

package com.example.widgettest

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.RemoteViews
import android.widget.RemoteViewsService
import com.google.gson.Gson

private const val REMOTE_VIEW_COUNT: Int = 2

data class WidgetItem(
    val id: String,
)

class ExampleWidgetService : RemoteViewsService() {

    override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
        return ExampleWidgetService1(this.applicationContext, intent)
    }
}

class ExampleWidgetService1(private val context: Context, intent: Intent) :
    RemoteViewsService.RemoteViewsFactory {

    private lateinit var exampleData: List<WidgetItem>

    override fun onCreate() {
        exampleData = List(REMOTE_VIEW_COUNT) { index -> WidgetItem("$index!") }
        val test = 123
    }

    override fun onDataSetChanged() {
    }

    override fun onDestroy() {
    }

    override fun getCount(): Int {
        return exampleData.size
    }

    override fun getItemId(p0: Int): Long {
        return p0.toLong()
    }

    override fun getViewAt(p0: Int): RemoteViews {

        return RemoteViews(context!!.packageName, R.layout.newwidgetentry).apply {

            setTextViewText(R.id.newwidgetentry, exampleData[p0].id)

       

     val fillIntent = Intent().apply {
                    Bundle().also { extras ->
                        extras.putInt(clickedCategory, p0)
                        putExtra("test",extras)
                    }
                }

  /*val fillIntent = Intent().apply {
                putExtra(clickedCategory, p0)
            }*/
            setOnClickFillInIntent(R.id.newwidgetentry, fillIntent)
        }
    }

    override fun getLoadingView(): RemoteViews? {
        return null
    }

    override fun getViewTypeCount(): Int {
        return 1
    }


    override fun hasStableIds(): Boolean {
        return true
    }

}

I really appreciate everykind of help!!! Thank you already in advance!!

aronyi
  • 73
  • 8
  • and where did you put extras? also where is `setPendingIntentTemplate` – Selvin Mar 11 '22 at 13:02
  • It is commented out ... so it doesn't run – Selvin Mar 11 '22 at 13:06
  • I know, I did it for testing purpose, doesn't matter when I comment it in. Already tried that, as I mentioned. The setPendingIntentTemplate I'm setting in the onUpdate of the class newwidget – aronyi Mar 11 '22 at 13:07
  • this is all wrong ... you should pass into `setPendingIntentTemplate` intent obtained from `PendingIntent.getXXX` and you are passing your own new intent `Intent().run {}` – Selvin Mar 11 '22 at 13:32
  • for the PendingIntent I only get 4 options: getService, getBroadcast, readPendingIntentOrNullFromParcel, get Activity. How can I retrieve the PendingIntentTemplate intent, which I have created in the onUpdate function? – aronyi Mar 11 '22 at 14:08
  • nevermind ... i don't know kotlin does `.run { }` return last statment ? if so then this language is pretty stupid ... as you can read from code if you have `() -> void` or `() -> somevalue` – Selvin Mar 11 '22 at 14:14
  • I have no idea, too.. I was first doing it a different way, without alle runs and applys... But since they are doing the same in the official google documentation, I thought that might be the cause, that it is not working... :D https://developer.android.com/guide/topics/appwidgets/collections – aronyi Mar 11 '22 at 14:25
  • from the example it's clear that you should set action where you have `//action= stackviewButton` and you should not provide action in intent passed to `setOnClickFillInIntent` – Selvin Mar 11 '22 at 14:34
  • yes you are right, this was orginating from the many other tries to make it work. I just updated the code again. So that it is identical to the one in the documentation – aronyi Mar 11 '22 at 14:40
  • now test3 and clickedCat2 in the onReceive function are null when I click on the card... – aronyi Mar 11 '22 at 14:42
  • in example there is `putExtras(extras)` not `putExtra("test",extras)` ... if you use `putExtra("test",extras)` then you should take bundle extra called "test" and then `clickedCategory` from this bundle like `intent?.getBundleExtra("test")?.getInt(clickedCategory, 0)` – Selvin Mar 11 '22 at 14:51
  • even with this, clickedCat2 is still Null var clickedCat2 = intent?.getBundleExtra("test")?.getInt(clickedCategory,-1) And of course i already tried it without giving the bundle an additional descriptor – aronyi Mar 11 '22 at 14:54
  • or theoriticaly this should work also `val fillInIntent = Intent().apply { putExtra(clickedCategory, p0) }` with your `val clickedCat: Int? = intent?.getIntExtra(clickedCategory, 0)` – Selvin Mar 11 '22 at 14:55
  • with that what you just posted, it should be this in the onReceive (clickedCat2: Int? = intent?.getIntExtra(clickedCategory, -1)). But it still stays by the default value (-1) when I click on a card.. – aronyi Mar 11 '22 at 14:59
  • are you sure that `clickedCategory` has the same value in both classes? – Selvin Mar 11 '22 at 15:01
  • I just have put the 2 options inside the code, the bundle option active the one without the bundle commented out. Yes clickedCatergory always stays "com.example.myapplication.buttonpress.id". I just proofed it via debugging. – aronyi Mar 11 '22 at 15:09
  • And I also remember that I tried it once just with an matching hardcoded string.. – aronyi Mar 11 '22 at 15:13

1 Answers1

8

I've the same issue, finally I resolve this issue!

Just change setPendingIntentTemplate's pending intent flags to PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE.

HearSilent
  • 96
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Mar 17 '22 at 16:17
  • wow, great! This is working now!!! Thank you very much for sharing the solution. I feel so free to add another question right away: Do you get an notification every single time you press on a card? In my case it seems like that the stackview widget is only recognizing every 5th click on a card or so... But the debugger in the onReceive function is getting called with every single click on a card... – aronyi Mar 19 '22 at 00:34
  • @aronyi Sorry, in my case can recognize every click event. – HearSilent Mar 20 '22 at 09:18