react-native-android-widget icon indicating copy to clipboard operation
react-native-android-widget copied to clipboard

Create custom Intent

Open OPavliuk-delhaize opened this issue 1 year ago • 3 comments

Thanks to the great library, it has greatly simplified and increased the development speed of many applications!

But because we support many applications, we had a small problem with the click on the widget. Widgets have the same basis and differ due to the provision of Flavors. However, they all have the same package name.

When we configure a widget in Manifest we use it

 <intent-filter>
     <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
     <action android:name="com.myPackageName.WIDGET_CLICK" />
 </intent-filter>

And then myPackageName is used in your library when we make click

Intent intent = new Intent(mContext.getPackageName() + ".WIDGET_CLICK");

Therefore, when we have several applications when clicking on a widget, a pop-up appears and asks us to select an application to launch.

I have an idea.

import com.reactnativeandroidwidget.RNWidgetProvider

class MyWidgetProvider : RNWidgetProvider() {
}

Could we set our variable in the constructor or some other way, which will be used instead of WIDGET_CLICK if it exists?

OPavliuk-delhaize avatar Aug 01 '24 15:08 OPavliuk-delhaize

Why does android show the app chooser if the applications have different package name? Is there a way to force the intent to be handled by a given application?

You mentioned multiple applications, what pattern do you have for the package names of the applications?

Also, what is your clickAction when you get the chooser?

sAleksovski avatar Aug 05 '24 21:08 sAleksovski

Hello, sorry for the long answer. Yes, you turned out to be right in android/app/build.gradle we set different names for different customers in productFlavors -> flavorName -> applicationId.

Several applications were triggered by clicking because there were problems with deep-link settings. However, there were still problems when several applications of the same customer were assembled for different environments. When updating applications, the widgets were triggered by incorrect updates. In one application, the user was logged in, but in the other not, and the widgets had a conflict.

The problem was completely solved due to the following actions

  1. Using the library patch-package were made changes
diff --git a/node_modules/react-native-android-widget/android/src/main/java/com/reactnativeandroidwidget/RNWidgetProvider.java b/node_modules/react-native-android-widget/android/src/main/java/com/reactnativeandroidwidget/RNWidgetProvider.java
index 889a8b4..bb4a7df 100644
--- a/node_modules/react-native-android-widget/android/src/main/java/com/reactnativeandroidwidget/RNWidgetProvider.java
+++ b/node_modules/react-native-android-widget/android/src/main/java/com/reactnativeandroidwidget/RNWidgetProvider.java
@@ -20,6 +20,7 @@ import org.json.JSONObject;
 import java.util.concurrent.TimeUnit;
 
 public class RNWidgetProvider extends AppWidgetProvider {
+    protected String click_widget = "WIDGET_CLICK";
     @Override
     public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
         super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
@@ -210,7 +211,7 @@ public class RNWidgetProvider extends AppWidgetProvider {
                 .build();
         }
 
-        Data data = buildData(context, widgetId, "WIDGET_CLICK", additionalData);
+        Data data = buildData(context, widgetId, this.click_widget, additionalData);
         startBackgroundTask(context, data);
     }
 }

After all the following operations, this may be redundant code, but others may find it useful.

  1. A script has been created that generates different classes for the widget and different manifests for all flavors in the project
const fs = require('fs')
const path = require('path')
const ejs = require('ejs')

const flavors = ['flavor1', 'flavor2', 'flavor3', 'flavor4']
const envs = ['qa1', 'qa2', 'qa3']
const build_types = ['release', 'debug']

const templatePathManifest = path.join(__dirname, 'templates', 'Manifest.ejs')
const templatePathMyWidgetClass = path.join(__dirname, 'templates', 'MyWidget.ejs')

const templateManifest = fs.readFileSync(templatePathManifest, 'utf8')
const templateMyWidgetClass = fs.readFileSync(templatePathMyWidgetClass, 'utf8')

flavors.forEach(flavor => {
  envs.forEach(env => {
    // eslint-disable-next-line complexity
    build_types.forEach(build_type => {
      try {
        const envName = env[0].toUpperCase() + env.slice(1)

        const folderName = flavor + envName + build_type

        const className = `MyWidget${flavor[0].toUpperCase()}${flavor.slice(
          1,
        )}${env[0].toUpperCase()}${env.slice(1)}${build_type}`

        const intentName = `WIDGET_CLICK_${flavor.toUpperCase()}_${env.toUpperCase()}_${build_type.toUpperCase()}`

        const outputFlavoursDir = path.join(
          __dirname,
          '..',
          '..',
          'android',
          'app',
          'src',
          folderName,
        )

        const outputManifestPath = path.join(outputFlavoursDir, `AndroidManifest.xml`)

        const outputWidgetClassDir = path.join(
          outputFlavoursDir,
          'java',
          'com',
          'project',
          'widget',
        )

        const outputWidgetClassPath = path.join(outputWidgetClassDir, `${className}.kt`)

        const renderedManifest = ejs.render(templateManifest, { className, intentName })
        const renderedWidgetClass = ejs.render(templateMyWidgetClass, { className, intentName })

        if (!fs.existsSync(outputFlavoursDir)) {
          fs.mkdirSync(outputFlavoursDir, { recursive: true })
        }
        if (!fs.existsSync(outputWidgetClassDir)) {
          fs.mkdirSync(outputWidgetClassDir, { recursive: true })
        }

        fs.writeFileSync(outputManifestPath, renderedManifest)
        fs.writeFileSync(outputWidgetClassPath, renderedWidgetClass)
      } catch (e) {
        console.log('🚀 ~ file: index.js:78 ~ e:', e)
      }
    })
  })
})

templates/Manifest.ejs

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <application>
    <receiver
      android:name=".widget.<%= className %>"
      android:exported="false"
      android:label="@string/widget_name">
      <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        <action android:name="com.packagename.<%= intentName %>" />
      </intent-filter>
      <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/widgetprovider_mywidget" />
    </receiver>
  </application>
</manifest>

Be careful with packagename

templates/MyWidget.ejs

package com.myprogect.widget

import com.reactnativeandroidwidget.RNWidgetProvider

class <%= className %> : RNWidgetProvider() {
    init {
        this.click_widget = "<%= intentName %>"
    }
}

These changes fixed my problem. A little later I will check whether it is possible to work correctly without using the patch package

OPavliuk-delhaize avatar Aug 23 '24 11:08 OPavliuk-delhaize

I'm not sure I completely understand. You are updating the click_action, but there are also problems with the update?

What is in your clickAction prop when you face this issue?

Can you create an example repo with two apps with conflicting click/update handlers?

sAleksovski avatar Sep 02 '24 15:09 sAleksovski

I'll close this since it looks like you have a workaround. If you have some time to create a reproducible example feel free to reopen this and I will take a look.

sAleksovski avatar Nov 11 '24 21:11 sAleksovski