I am able to start my MAUI Android App using URI,but when I have an instance of the app that is minimized and click on URI appears that a new instance of the app is being created and exception is thrown.
If my app is already running, I want to use that instance.
I have this in my manifest file :
<activity android:exported="false" android:allowBackup="true" android:supportsRtl="true" android:theme="#style/AppTheme" android:name="FF.Client.MAUI.MainActivity" android:debuggable="true" android:extractNativeLibs="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="flexyfit.bg"
android:pathPrefix="/video" />
</intent-filter>
</activity>
This is my MainActivity :
[IntentFilter(new[] { Intent.ActionView },
DataScheme = "https",
DataHost = "test.com",
DataPathPrefix = "/hello",
AutoVerify = true,
Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable, Intent.ActionView })]
public class MainActivity : MauiAppCompatActivity
{
public static MainActivity Instance { get; set; }
public event EventHandler<object> PrivateNotificationSent = delegate { };
public MainActivity()
{
Instance = this;
}
protected override void OnCreate(Bundle savedInstanceState)
{
AppCompatDelegate.DefaultNightMode = AppCompatDelegate.ModeNightNo;
Intent intent = this.Intent;
this.OnNewIntent(intent);
base.OnCreate(savedInstanceState);
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
var data = intent.DataString;
if (intent.Action != Intent.ActionView) return;
if (string.IsNullOrWhiteSpace(data)) return;
if (data.Contains("/hello"))
{
Shell.Current.GoToAsync(nameof(SomePage))
}
}
}
If my app is already running, I want to use that instance.
You can add the android:launchMode="singleInstance" into the manifest file, such as:
<activity android:exported="false" android:allowBackup="true" android:launchMode="singleInstance" >
The default value of the android:launchMode is standard. If you don’t set any launch mode to your activity, it will use the standard mode by default. It creates a new instance of activity every time even if activity instance is already present.
For more information, you can check the official document about the android:launchMode.
Related
I'm currently trying to implement mollie payment into the flutter framework. For that I want to build a plugin in Java. So mollie has a very good documentation how to implement their API into an android app with Java. So the problem is when I hit the payment button in my app the browser opens with the correct checkout page. After the customer select his preferred payment method mollie goes back to my app, but I always get a empty query...
So this is my code for the flutter plugin
public class MolliePlugin implements MethodCallHandler, PluginRegistry.ActivityResultListener {
Activity activity;
Result activeResult;
Context context;
Intent intent;
boolean getData;
private static final int REQUEST_CODE = 0x1337;
MolliePlugin(Registrar registrar){
activity = registrar.activity();
}
/** Plugin registration. */
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "mollie");
channel.setMethodCallHandler(new MolliePlugin(registrar));
}
#Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("startPayment")) {
activeResult = result;
String checkout = call.argument("checkoutUrl");
startPayment(checkout);
}
else {
result.notImplemented();
}
}
/// Start the browser switch with a ACTION_VIEW
void startPayment(String checkoutUrl) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW,Uri.parse(checkoutUrl));
activity.startActivity(browserIntent);
}
So in the docs of mollie is written that I have to put the following code into the onCreate() function in my MainActivity:
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//...
Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
Uri uri = intent.getData();
String paymentId = uri.getQueryParameter("id");
// Optional: Do stuff with the payment ID
}
}
So when I put this into the onCreate() in my MainActivity of my Flutter app I get always an ACTION_RUN back after I was routed back to my app. So I used the onNewIntent() function which gives me the correct action after coming back to my app (any ideas why?):
public class MainActivity extends FlutterActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d("Action is: ",intent.getAction());
String paymentId = "No Id";
if (Intent.ACTION_VIEW.equals(intent.getAction()) && intent != null) {
Uri uri = intent.getData();
// This has no data...
Log.d("query ",intent.getDataString());
paymentId = uri.getQueryParameter("id");
// Optional: Do stuff with the payment ID
}
}
}
So here I get an empty query. the intent.getData() only returns my returnUrl which I have to set up in my AndroidManifest (see below). The returnUrl works fine but it has no data included after checking out and swichting back to the app...
My AndroidManifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.plugon.mollie_example">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="mollie_example"
android:icon="#mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="#style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- This keeps the window background of the activity showing
until Flutter renders its first frame. It can be removed if
there is no splash screen (such as the default splash screen
defined in #style/LaunchTheme). -->
<meta-data
android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
android:value="true" />
<intent-filter>
<data
android:host="payment-return"
android:scheme="molli" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
</activity>
</application>
</manifest>
Any ideas what I'm doing wrong?
I implemented deep linking in my Android app with push notification but when I open the app with the push notification deep linking message rather than the Deep Linking Activity, the app opens the launch activity inconsistently. I tried to change android:launchMode="singleTop" but did not work. My Deep Linking Activity and Android Manifest code parts are like below.
Deep Linking Activity:
[Activity(Label = "DeepLinkingActivity")]
public class DeepLinkingActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
if (SessionContext.DeepLinkingMessageContent == null)
{
CheckDeepLinkingContent(Intent);
}
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
if (SessionContext.DeepLinkingMessageContent == null)
{
CheckDeepLinkingContent(intent);
}
}
void CheckDeepLinkingContent(Intent intent)
{
if (intent.Data != null)
{
var intentData = Intent.Data;
var hostData = intentData.Host;
var path = intentData.Path;
var pathContent = path.Split('/');
//...
//processing content of the deep linking message
SessionContext.DeepLinkingMessageContent = deepLinkingContent;
StartActivity(typeof(LoginView));
Finish();
}
}
Part of Android Manifest:
<activity android:name="myapp.android.views.DeepLinkingActivity" android:launchMode="singleTop">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myappscheme" android:host="apphost"/>
</intent-filter>
</activity>
Preconditions
1. App starts with LinkActivity, at this point we have no deep link intent, it's ok.
Main activity launched. There we are able to click the deep link.
By clicking on deep link opens LinkActivity, uri is correct, referringParams json is not empty (ok). But...
When we replaying step 2: uri is correct, but the reffering params are empty: "{}"; All other tries are with the same result.
Only when we pausing the app (for example switching to the recent apps menu) and then returning to the app - deep link works as expected, but only at first try. May be some issues with the session close (but in the current version of the sdk it self controls session close)
public class LinkActivity extends AppCompatActivity {
private static final String TAG = LinkActivity.class.getSimpleName();
#Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
}
#Override
protected void onStart() {
super.onStart();
Uri uri = getIntent().getData();
Log.w(TAG, "uri: " + uri);
Branch.getInstance().initSession(new Branch.BranchReferralInitListener() {
#Override
public void onInitFinished(JSONObject referringParams, BranchError error) {
Log.w(TAG, "json: " + referringParams);
startActivity(new Intent(LinkActivity.this, MainActivity.class));
}
}, uri, this);
}
}
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
public class BranchApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Branch.enableLogging();
Branch.getAutoInstance(this);
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.myapp">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".BranchApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".LinkActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="myapp.link"
android:scheme="https" />
</intent-filter>
</activity>
<activity android:name=".MainActivity"/>
<meta-data
android:name="io.branch.sdk.BranchKey"
android:value="#string/branch_io_live_key" />
<meta-data
android:name="io.branch.sdk.BranchKey.test"
android:value="#string/branch_io_test_key" />
<meta-data
android:name="io.branch.sdk.TestMode"
android:value="false" />
</application>
</manifest>
implementation "io.branch.sdk.android:library:2.14.3"
Update:
Even with android:launchMode="singleInstance" for LinkActivity steel reproduces (I don't think this is the case).
Udpate2:
Bhardwaj mentioned that no need to call initSession when we initing Branch via getAutoInstance. But how to get refferingParams from uri in that case?
Update3:
From the Branch.checkIntentForSessionRestart doc:
Check for forced session restart. The Branch session is restarted if
the incoming intent has branch_force_new_session set to true. This is
for supporting opening a deep link path while app is already running
in the foreground. Such as clicking push notification while app in
foreground.
So, My desired behavior is matches this description. But how to force session restart?
You can try as mentioned below :-
Branch.getAutoInstance(this) -> Branch.getAutoInstance(this, true)
Branch.getInstance(context) -> Branch.getInstance()
Do not call initSession when you have getAutoInstance()
if(!initiatedBranchDeepLinks) {
// Configure Branch.io
initiatedBranchDeepLinks = true;
Branch branch = Branch.getInstance();
branch.initSession(new Branch.BranchReferralInitListener(){
#Override
public void onInitFinished(JSONObject referringParams, BranchError error) {
if (error == null) {
// params are the deep linked params associated with the link that the user clicked -> was re-directed to this app
// params will be empty if no data found
// ... insert custom logic here ...
String message = "Branch.io onInitFinished. Params: " + referringParams.toString();
Log.d(TAG, message);
} else {
Log.i(TAG, error.getMessage());
}
}
}, this.getIntent().getData(), this);
}
Here is Branch Test Bed app:
https://github.com/BranchMetrics/android-branch-deep-linking/tree/master/Branch-SDK-TestBed
You can use this as a reference and see what you are doing incorrectly.
This could be caused by your Manifest configuration. In your <activity> tag, you should include android:launchMode="singleTask". See this section of our docs. This may explain why you are receiving the parameters the first time, but not receiving them on a re-open.
I have an application that accepts deeplink.
Manifest.xml:
<activity
android:name=".activities.unsigned.MagicLink"
android:label="Some test">
<intent-filter android:label="Test">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.myapp" />
</intent-filter>
</activity>
<activity
android:name=".activities.unsigned.MainScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Activity:
public class MagicLink extends BusAppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
if (intent != null && intent.getAction() != null) {
Uri data = intent.getData();
ServicesApi servicesApi = ServicesApi.init(this);
servicesApi.setSessionId(data.getQueryParameter(HttpRemoteApi.SESSION_ID));
startActivity(new Intent(this, LoginActivity.class));
}
}
}
This thing works perfectly if user use it. Well I want to create a test for it now. So I write something like this:
androidTest:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class LoginTest {
#Rule
public final ActivityTestRule<MainScreen> main = new ActivityTestRule<>(MainScreen.class);
#Test
public void checkSmth() {
clickMagicLink();
//...
}
private void clickMagicLink() {
String url = "com.myapp://login?session_id="+utils.getSessionId();
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
main.launchActivity(i);
}
}
But instead of starting MagicLink activity this thing starts MainScreen activity (which is MAIN). What do I do wrong?
P.s. I also saw something like this: new ActivityTestRule<>(MainScreen.class,true, false);. But with this constructor my test start, but android app doesn't (I mean emulator starts but app doesn't)
ActivityTestRule.launchActivity() always starts the activity being tested. You cannot use it to start any other activity. In this case, it will always start MainActivity. The Intent parameter is passed to the activity. This allows you to send extras during a test. The intent is not used to select which activity to launch.
Also note that the docs say
Don't call this method directly, unless you explicitly requested not to lazily launch the Activity manually using the launchActivity flag in ActivityTestRule(Class, boolean, boolean).
If you want to test your MagicLink activity, you can use ActivityTestRule<MagicLink>:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class MagicLinkTest {
#Rule
public final ActivityTestRule<MagicLink> main = new ActivityTestRule<>(MainScreen.class, false, false);
#Test
public void testMagicLink() {
String url = "com.myapp://login?session_id="+utils.getSessionId();
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
main.launchActivity(i);
// assertions go here
}
}
You can also use ActivityTestRule<MainScreen> but you have to simulate the exact same actions as an actual user.
I have an app with several activities, they all have a launch intent-filter in the manifest so they can show several icons on the launcher, there is a main activity and the rest of them are disabled by default with android:enabled="false" here is a part of my manifest:
<activity
android:name="com.myapp.MainActivity"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.myapp.Activity_1"
android:icon="#mipmap/ic_launcher"
android:label="#string/secondary_activity"
android:enabled="false">// HERE I DISABLE THE ACTIVITY
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
I found a way to enable or disable the other activities using the following code:
public static void enableComponent(Context context, Class<?> componentClass, boolean isEnable) {
int enableFlag = isEnable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
context.getPackageManager().setComponentEnabledSetting(new ComponentName(context, componentClass), enableFlag, PackageManager.DONT_KILL_APP);
}
private void setupDetailsOverviewRowPresenter() {
detailsPresenter.setOnActionClickedListener(new OnActionClickedListener() {
#Override
public void onActionClicked(Action action) {
if (action.getId() == ACTION_ENABLE){
mSelectedApp = (App) getActivity().getIntent().getSerializableExtra(DetailsActivity.APP);
enableComponent(mContext, com.myapp.Activity_1.class, true);
}
}else if (action.getId() == ACTION_DISABLED){
mSelectedApp = (App) getActivity().getIntent().getSerializableExtra(DetailsActivity.APP);
enableComponent(mContext, com.myapp.Activity_1.class, false);
}
}
});
}
This works perfectly by enabling or disabling the activity with the ACTION_ENABLE or ACTION_DISABLE buttons, but that's not good for usability, instead I would like to use just one button to enable or disable the activity.
What I need to know is how to get the status of the activity, so if the activity is android:enabled="false" display the button with ACTION_EANBLE and if the activity is android:enabled="true" display the button with ACTION_DISABLE.
You can query the PackageManager to determine if a component is enabled or not:
PackageManager pm = getPackageManager();
ComponentName cn = new ComponentName(...);
ActivityInfo info = pm.getActivityInfo(cn, 0);
if (info != null && info.enabled) {
// Component is enabled
...
} else {
// Component is disabled
...
}