I'm making a widget for my WebView app, and it's got a list of buttons on it. Currently, It's firing an intent whenever their pressed. In that intent, I'm putting some string extra's, but when the onNewIntent receives the intent, the value for the extra is NULL. So I'm stuck on receiving the actual string extra.
Here's the code on my list provider:
override fun getViewAt(positionIndexNum: Int): RemoteViews {
........
val extrasObj = Bundle()
extrasObj.putString("shortcutUrl", listViewUrlArr[positionIndexNum]) // I've tried hardcoding this part and it still returns null.
extrasObj.putString("shortcutJs", listViewJsArr[positionIndexNum])
extrasObj.putString("shortcutId", listViewIdArr[positionIndexNum])
val fillInIntentObj = Intent()
fillInIntentObj.putExtras(extrasObj)
viewObj.setOnClickFillInIntent(listViewItemId, fillInIntentObj)
return viewObj
}
Here's the code from the onNewIntent function:
override fun onNewIntent(intentObj: Intent) {
super.onNewIntent(intentObj)
val bundle = intentObj.extras
if (bundle != null) {
for (key in bundle.keySet()) {
Log.e("TAG", key + " : " + if (bundle[key] != null) bundle[key] else "NULL")
}
}
.....
}
That outputs in the logcat:
shortcutUrl : NULL
shortcutId : NULL
shortcutJs : NULL
I've also tried: intentObj.getStringExtra("shortcutId") which still returns NULL
EDIT:
I also have this PendingIntent code in the updateAppWidget function:
val clickIntent = Intent(contextObj, MainActivity::class.java)
val clickPI = PendingIntent.getActivity(contextObj, 0,
clickIntent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT);
viewsObj.setPendingIntentTemplate(R.id.widget_list, clickPI)
I finally found a fix for this, I'm not sure how it really works but I changed:
PendingIntent.FLAG_IMMUTABLE
to
PendingIntent.FLAG_MUTABLE
in the PendingIntent. Hopefully this helps someone else!
Here is a full answer which I posted on another question:
Application widget with bundle?
Read the Intent in MyActivity:
private fun readIntent() {
val intentExtras: Bundle? = intent.extras
intentExtras?.let {
val intentMessage: String? = intentExtras.getString(APPWIDGET_INTENT_MESSAGE)
println(intentMessage)
}
}
Here is a method in Kotlin to get pending intent to open your activity
fun getPendingIntentMyActivity(context: Context, message: String): PendingIntent {
val intent = Intent(context, MyActivity::class.java)
intent.action = APPWIDGET_INTENT
intent.data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))
val extras = Bundle().apply {
putString(APPWIDGET_INTENT, APPWIDGET_OPEN_APP)
putString(APPWIDGET_INTENT_MESSAGE, message)
}
intent.putExtras(extras)
return PendingIntent.getActivity(context, 0, intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
}
Then set it in the Widget
remoteViews.setOnClickPendingIntent(R.id.rootView, getPendingIntentMyActivity(context, "Hello World")
Related
I am using this package with customisation home_widget and trying to build a home widget with number buttons like this image.
I am trying to achieve it by the following code
My WidgetProvider.kt
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
appWidgetIds.forEach { widgetId ->
val views = RemoteViews(context.packageName, R.layout.widget_layout).apply {
// Open App on Widget Click
val pendingIntent = HomeWidgetLaunchIntent.getActivity(context,
MainActivity::class.java)
setOnClickPendingIntent(R.id.widget_root, pendingIntent)
val counter = widgetData.getString("_number", "")
Log.i("onMethodCall", "onMethodCall: $counter")
print("onMethodCall: $counter")
var counterText = "$counter"
setTextViewText(R.id.tv_counter, counterText)
val pendingIntent2 = HomeWidgetLaunchIntent.getActivity(context,
MainActivity::class.java)
setOnClickPendingIntent(R.id.bt_update, pendingIntent2)
val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(context, Uri.parse("myAppWidget://onNumberBtnClick"))
setOnClickPendingIntent(R.id.bt_one, backgroundIntent)
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
I am passing intent as "onNumberBtnClick" and this is my broadcast function
fun getBroadcast(context: Context, uri: Uri? = null): PendingIntent {
val intent = Intent(context, HomeWidgetBackgroundReceiver::class.java)
intent.data = uri
intent.action = HOME_WIDGET_BACKGROUND_ACTION
var flags = PendingIntent.FLAG_UPDATE_CURRENT
if (Build.VERSION.SDK_INT >= 23) {
flags = flags or PendingIntent.FLAG_IMMUTABLE
}
return PendingIntent.getBroadcast(context, 0, intent, flags)
}
in my "HomeWidgetPlugin" class I have set that value in shared preference.
"onNumberBtnClick" -> {
if (call.hasArgument("id") && call.hasArgument("data")) {
Log.i("onMethodCallHere", "onMethodCallHere: ")
print("onMethodCallHere")
val id = call.argument<String>("id")
val data = call.argument<Any>("data")
val prefs = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE).edit()
if(data != null) {
when (data) {
is Boolean -> prefs.putBoolean(id, data)
is Float -> prefs.putFloat(id, data)
is String -> prefs.putString(id, data)
is Double -> prefs.putLong(id, java.lang.Double.doubleToRawLongBits(data))
is Int -> prefs.putInt(id, data)
else -> result.error("-10", "Invalid Type ${data!!::class.java.simpleName}. Supported types are Boolean, Float, String, Double, Long", IllegalArgumentException())
}
} else {
prefs.remove(id);
}
result.success(prefs.commit())
}else {
result.error("-1", "InvalidArguments saveWidgetData must be called with id and data", IllegalArgumentException())
}
}
In main.dart I handled callback with workmanager plugin as in the example of the home widget plugin
Future<void> backgroundCallback(Uri? uri) async {
if (uri?.host == 'onNumberBtnClick') {
await HomeWidget.getWidgetData<String>('_number', defaultValue: "1").then((value) {
print("num:$value");
});
await HomeWidget.saveWidgetData<String>('_number', "13");
await HomeWidget.updateWidget(name: 'AppWidgetProvider', iOSName: 'AppWidgetProvider');
}
}
Can someone please help me with what I am missing or how can I achieve this?
I was creating a simple notification app with Alarm Manager. The alarm manager trigger for 1 minute and it should give a notification. but when I tapped on the notification moves to details screen with first content.
It not changing.
Below broadcast receiver calls each 1 minute.
notification changing with different data but not in details screen. that is the problem.
AlarmReceiver
class AlarmReceiver : BroadcastReceiver()
{
override fun onReceive(context: Context?, intent: Intent?)
{
var dbHelper = context?.let { DbHelper(it) }
val question = dbHelper?.getQuestion((0..1280).random())
val i = Intent(context, SecondActivity::class.java)
i.putExtra("LAW", question)
i!!.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
val pendingIntent = PendingIntent.getActivity(context, 0, i, 0)
val builder = NotificationCompat.Builder(context!!, "foxandroid")
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("${question!!.type} ${question!!.code} ${question!!.subcode} ${question!!.shortDesc}")
.setContentText("${question!!.fullDesc}")
.setAutoCancel(true)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
val notificationManager = NotificationManagerCompat.from(context)
notificationManager.notify(123, builder.build())
}
}
Below class shows the notification data in detail.
Second Activity
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
val law = if (Build.VERSION.SDK_INT >= 33) {
intent.getSerializableExtra("LAW", Question::class.java)
} else {
intent.getSerializableExtra("LAW") as Question
}// as? Question
Log.e("law:", "${law!!.shortDesc}")
if (law != null) {
textTitle.text = "${law.type} ${law.code} ${law.subcode} ${law.shortDesc}"
}
if (law != null) {
textContent.text = law.fullDesc
}
button.setOnClickListener {
val intent= Intent(this,WebActivity::class.java)
intent.putExtra("LAW", "${law?.type} ${law?.code} ${law?.subcode}")
startActivity(intent)
}
}
}
you should use Activity#onNewIntent,
Your Activity was already created, when you start activity again,the Activity may be not run onCreate lifecycle function, and run onNewIntent. OnNewIntent somelike onCreate, they are lifecycle function.
You just try log extra bundle in Intent at OnNewIntent, and you will know.
I have a list view in my home screen widget and I want to launch a pending intent when the item is clicked. I referred to this and set a fillInIntent for every list item and then set a pending intent template which triggers the broadcast receiver of the widget:
class WidgetProvider : HomeWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
appWidgetIds.forEach { widgetId ->
val views = RemoteViews(context.packageName, R.layout.widget_layout).apply {
val todosStr = widgetData.getString("todos", "null")
val todos = ArrayList<HashMap<String, Any>>()
val todosRemoteView = RemoteViews.RemoteCollectionItems.Builder()
if(todosStr != "null"){
val jObj = JSONObject(todosStr)
val jsonArry = jObj.getJSONArray("todos")
for (i in 0 until jsonArry.length()) {
val todo = HashMap<String, Any>()
val obj = jsonArry.getJSONObject(i)
todo["id"] = obj.getInt("id")
todos.add(todo)
val view = RemoteViews(context.packageName, R.layout.each_todo).apply {
setTextViewText(R.id.each_todo_container_text, todo["taskName"].toString())
val fillInIntent = Intent().apply {
Bundle().also { extras ->
extras.putInt("todo_id", todo["id"].toString().toInt())//this isn't working for some reason
putExtras(extras)
}
}
Log.d("debugging", "id received is ${todo["id"].toString()}" )
setOnClickFillInIntent(R.id.each_todo_container_text, fillInIntent)
}
todosRemoteView.addItem(todo["id"].toString().toInt().toLong(), view)
}
}
setRemoteAdapter(
R.id.todos_list,
todosRemoteView
.build()
)
val pendingIntentx: PendingIntent = Intent(
context,
WidgetProvider::class.java
).run {
PendingIntent.getBroadcast(context, 0, this, PendingIntent.FLAG_IMMUTABLE or Intent.FILL_IN_COMPONENT)
}
setPendingIntentTemplate(R.id.todos_list, pendingIntentx)
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
override fun onReceive(context: Context?, intent: Intent?) {
val viewIndex: Int = intent!!.getIntExtra("todo_id", 0)
Log.d("debugging", "an item is clicked $viewIndex")
//the default value gets used, which means that the receiver isn't receiving the intent data
super.onReceive(context, intent)
}
}
The problem is that the receiver isn't receiving the data which was put in the fillInIntent, the default value 0 is getting printed by the last statement. I also tried setting the FILL_IN_COMPONENT flag as suggested here, but that didn't work. Where am I going wrong?
I am coding a simple app that measures all available sensors of the android device (Wifi, BT, etc). One of them is the user activity (via ActivityRecognition API), but I can't make it works properly.
I code a class to do everything related to user activity. I want to get only 4 states and one attribute to store the current one:
var VEHICLE = "vehicle"
var WALKING = "walking"
var STILL = "still"
var UNKNOWN = "unknown"
private var current: String? = null
It also includes a BroadcastReceiver object to handle activity transitions:
private var recognitionHandler = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (ActivityRecognitionResult.hasResult(intent)) {
val result = ActivityRecognitionResult.extractResult(intent)
val activity = result.mostProbableActivity
current = when(activity.type) {
DetectedActivity.IN_VEHICLE,
DetectedActivity.ON_BICYCLE -> VEHICLE
DetectedActivity.WALKING,
DetectedActivity.RUNNING -> WALKING
DetectedActivity.STILL -> STILL
else -> UNKNOWN
}
}
}
}
The class also have two methods to define the intent and request:
private fun createIntent() : PendingIntent {
val intent = Intent(context, recognitionHandler.javaClass)
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
context.registerReceiver(recognitionHandler, IntentFilter())
return pendingIntent
}
private fun createRequest() : ActivityTransitionRequest {
val types = listOf(
DetectedActivity.IN_VEHICLE,
DetectedActivity.WALKING,
DetectedActivity.RUNNING,
DetectedActivity.ON_BICYCLE,
DetectedActivity.STILL
)
val transitions = mutableListOf<ActivityTransition>()
types.forEach { activity ->
transitions.add(
ActivityTransition.Builder()
.setActivityType(activity)
.setActivityTransition(ActivityTransition.ACTIVITY_TRANSITION_ENTER)
.build()
)
}
return ActivityTransitionRequest(transitions)
}
And also one to start listening:
override fun start(onResult: (res: String?) -> Unit) {
// ...
intent = createIntent()
val request = createRequest()
ActivityRecognition.getClient(context)
.requestActivityTransitionUpdates(request, intent)
.addOnSuccessListener {
Log.d("UserActivity Service info", "listening...")
}
.addOnFailureListener { e ->
Log.d("UserActivity Service error", e.toString())
}
// ...
}
The problem is that the current attribute is always null. I think I have some issues with intent or handler registration, but I have no idea where.
Does someone have any comments? :)
Thanks!
This is your problem. In this code from createIntent():
val intent = Intent(context, recognitionHandler.javaClass)
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
context.registerReceiver(recognitionHandler, IntentFilter())
return pendingIntent
You return a PendingIntent that you use in the call to requestActivityTransitionUpdates(). However, that PendingIntent refers to a dynamically created inner class (your BroadcastReceiver) and Android cannot instantiate that class.
You also additionally call registerReceiver(), however you pass an empty IntentFilter in that call so the registered BroadcastReceiver is never called.
To fix the problem, you can either provide a correctIntentFilter that matches your PendingIntent OR you can refactor your BroadcastReceiver into a proper class (not a private inner class) and make sure that you've added the BroadcastReceiver to your manifest and make it publicly available (exported="true").
Here's an example of how to do this using a BroadcastReceiver:
https://steemit.com/utopian-io/#betheleyo/implementing-android-s-new-activity-recognition-transition-api
I want to get a string extra in another activity from an intent. This is the way to create my intent
val intent = Intent(this, Main2Activity::class.java)
intent.putExtra("samplename", "abd")
startActivity(intent)
How can i get the value of this intent in the another activity
Answer found, in the next activity, you have to do this to get the string:
val ss:String = intent.getStringExtra("samplename").toString()
LOAD
val value: String = txt_act_main.text.toString() // or just your string
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("value", value)
startActivity(intent)
//option 2 all inner classes should be implemented to Serializable
getIntent().putExtra("complexObject", clickedTitle);
GET
var bundle :Bundle ?=intent.extras
var message = bundle!!.getString("value") // 1
var strUser: String = intent.getStringExtra("value") // 2
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
//option 2
var myProg = intent.getSerializableExtra("complexObject") as MenuModel
IMPLICIT (To Share with other apps)
val value: String = txt_act_main.text.toString()
var intent = Intent()
intent.action = Intent.ACTION_SEND
intent.putExtra(Intent.EXTRA_TEXT, value)
intent.type="text/plain"
startActivity(Intent.createChooser(intent,"Share to "))
Can use this code :
val bundle = intent.extras
var sampleName: String = ""
if (bundle != null) {
sampleName = bundle.getString("samplename")
}
you can check if the intent value is null or not
val bundle :Bundle ?=intent.extras
if (bundle!=null){
val message = bundle.getString("object") // 1
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
In Main2Activity you can have your code like this :
val intent = getIntent();
val myValue = intent.getStringExtra("key")
Log.d(TAG,"myValue"+myValue)
The accepted answer doesn't solve the case where the intent is not there. Because when the key is not exist in intent, getStringExtra() will give you null even its signature indicates a String rather than a String?.
You can use val text:String = intent.getStringExtra(intentKey) ?: "" to make sure no NPE happened.
But one more answer here:
This is for the case where, you try to retrieve the string from intent, if the value is there, we get the value, otherwise, it will go back to the previous screen because this intent is critical. Something wrong will happen but we don't want to crash the activity.
private fun getStringFromIntentOrShowError(intentKey: String):String {
val text:String? = intent.getStringExtra(intentKey)
if (text == null) {
showDialog(
"Error",
"No $intentKey found"
) {
it.dismiss()
finish()
}
return ""
}
return text
}
// I use anko to show a dialog, you can use your one.
private fun showDialog(
title:String,
message:String,
yesButtonCallback: (d:DialogInterface) -> Unit
) {
alert(message, title){ yesButton{
yesButtonCallback(it)
} }.show()
}
Then you can use it like this:
val text:String = getStringFromIntentOrShowError("asd")
and text will always have a value
You can use this simple Kotlin Extension
fun Intent.getData(key: String): String {
return extras?.getString(key) ?: "intent is null"
}
this Extension checks if the intent value is null, if the value is null it will return intent is null otherwise, it will return the value
use it like this
val username = intent.getData("username")
First you need to initialize the intent variable.
Then take out the data like we do in java :)
val intent: Intent = intent
var data = intent.getStringExtra("mydata")