URI start of MAUI Windows app creates a new instance. I need to have only one instance of app - windows

I am able to start my Windows MAUI app using an URI, and I can get the URI itself. But, it appears that a NEW instance of the app is being created. This is not ideal for me -- if my app is already running, I want to use that instance.
I have done something like this for a Xamarin.Forms app. I override OnActivated in Application class.
Re: my MAUI app, I'm not even clear on whether the issue is how I've done the "protocol" in package.appxmanifest, or if it is how I respond to lifecycle events.

The default behaviour is to run multiple instances of your app. You can make the app single-instanced by defining a customized class with a Main method as suggested in this blog post:
[STAThread]
static async Task Main(string[] args)
{
WinRT.ComWrappersSupport.InitializeComWrappers();
bool isRedirect = await DecideRedirection();
if (!isRedirect)
{
Microsoft.UI.Xaml.Application.Start((p) =>
{
var context = new DispatcherQueueSynchronizationContext(
DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
new App();
});
}
return 0;
}
private static async Task DecideRedirection()
{
bool isRedirect = false;
AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
ExtendedActivationKind kind = args.Kind;
AppInstance keyInstance = AppInstance.FindOrRegisterForKey("randomKey");
if (keyInstance.IsCurrent)
{
keyInstance.Activated += OnActivated;
}
else
{
isRedirect = true;
await keyInstance.RedirectActivationToAsync(args);
}
return isRedirect;
}
There is an open suggestion to simplify this process available on GitHub.

Related

Xamarin MacOS app launch at login

I'm trying to make my Xamarin MacOS App to run at login. Looking around I found only very old topics with no solutions.
Is there any solution available? Is feasible?
My App runs, by default with Administrator Privileges
Thanks
I wrote this article some time ago. It should still be valid https://shamsutdinov.net/2016/09/27/how-to-launch-at-login-your-xamarin-mac-sandboxed-application/
tl;dr;
Your app should run in the sandbox:
In your main app add this code
public class StartAtLoginOption
{
[DllImport("/System/Library/Frameworks/ServiceManagement.framework/ServiceManagement")]
static extern bool SMLoginItemSetEnabled(IntPtr aId, bool aEnabled);
public static bool StartAtLogin(bool value)
{
CoreFoundation.CFString id = new CoreFoundation.CFString("my.helper.app.bundle.id");
return SMLoginItemSetEnabled(id.Handle, value);
}
}
Create a helper app which only runs in background:
Start your main app from the helper app:
public override void DidFinishLaunching(NSNotification notification)
{
if (!NSWorkspace.SharedWorkspace.RunningApplications.Any(a => a.BundleIdentifier == "my.main.app.bundle.id"))
{
var path = new NSString(NSBundle.MainBundle.BundlePath)
.DeleteLastPathComponent()
.DeleteLastPathComponent()
.DeleteLastPathComponent()
.DeleteLastPathComponent();
var pathToExecutable = path + #"Contents/MacOS/LoginItemTestMain";
if (NSWorkspace.SharedWorkspace.LaunchApplication(pathToExecutable)) { }
else NSWorkspace.SharedWorkspace.LaunchApplication(path);
}
NSApplication.SharedApplication.Terminate(this);
}
This method works also for "non sandboxed" apps. I don't really like it but, for the moment, it's working:
public void SetAtLogin()
{
//Checking if the app is in the login items or not
var script = "tell application \"System Events\"\n get the name of every login item\n if login item \"AppNameTest\" exists then\n return true\n else\n return false\n end if\n end tell";
NSAppleScript appleScript = new NSAppleScript(script);
var errors = new NSDictionary();
NSAppleEventDescriptor result = appleScript.ExecuteAndReturnError(out errors);
var isLoginItem = result.BooleanValue;
if (!isLoginItem)
{
NSAppleScript login;
//AppleScript to add app to login items
script = "tell application \"System Events\"\n make new login item at end of login items with properties {name: \"AppNameTest\", path:\"/Applications/DayOne.app\", hidden:false}\n end tell";
login = new NSAppleScript(script);
var resul = login.ExecuteAndReturnError(out errors);
}
}

Xamarin.Forms App return data to calling App

So, either I am asking incorrectly, or it isn't possible, let's see which...
If my app (Xamarin.Forms) is launched from another app, in order to get a url from my app, how do I return that data to the calling app? I wrongly assumed SetResult and Finish, I also wrongly assumed StartActivityForResult, but there has to be a way to do this. I know how to get data INTO my app from another app, but not the same in return.
POSSIBLE PARTIAL SOLUTION -- UPDATE, FAILS
So I have to setup an interface in my PCL, and call the method from the listview item selected handler, in the Android app I can then do this:
Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_url"));
setResult(Activity.RESULT_OK, result);
finish();
(source: https://developer.android.com/training/basics/intents/filters.html)
Is this looking right, and how would I implement the same thing on iOS?
END
I deleted my previous question because I couldn't explain the problem clearly, so here goes.
I have a Xamarin Forms app, I want to use a section of this app as a gallery. Currently I have images displayed in a list, and I have an Intent filter set that launches this page when you select the app as the source for an image (such as upload image on Facebook).
My issue is that I don't know how to return the data (the selected image) back to the app / webpage that made the request. In android I understand that you would use StartActivityForResult and OnActivityResult to handle this, but I am using Xamarin Forms (Android, iOS, UWP) and can't really find a solution that could be used cross-platform.
Just a link to documentation that covers this would be great, but if you have an example then even better.
Thanks
EDIT
Here is the code used to launch the app, I am interested in getting data back from the Intent.ActionPick after the user has selected an image from a ListView, which is in a ContentPage in the PCL.
[Activity(Label = "", Icon = "#drawable/icon", Theme = "#style/DefaultTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
[IntentFilter(new[] { Intent.ActionSend }, Categories = new[] { Intent.CategoryDefault }, DataMimeType = #"*/*")]
[IntentFilter(new[] { Intent.ActionView, Intent.ActionPick, Intent.ActionGetContent }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryOpenable }, DataMimeType = #"*/*")]
public class MainActivity : FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
try
{
base.OnCreate(bundle);
CurrentPlatform.Init();
Xamarin.Forms.Forms.Init(this, bundle);
App _app = new App();
LoadApplication(_app);
if (Intent.Action == Intent.ActionSend)
{
var image = Intent.ClipData.GetItemAt(0);
var imageStream = ContentResolver.OpenInputStream(image.Uri);
var memOfImage = new System.IO.MemoryStream();
imageStream.CopyTo(memOfImage);
_app.UploadManager(memOfImage.ToArray()); //This allows me to upload images to my app
}
else if (Intent.Action == Intent.ActionPick)
{
_app.SelectManager(); //here is where I need help
}
else
{
_app.AuthManager(); //this is the default route
}
}
catch (Exception e)
{
}
}
It seems you cannot use remote URI to provide to calling app. Some posts I checked suggest to store the file locally and provide it's path to calling app. To avoid memory leak with many files stored I suggest to use the same file name then you will have only one file at any moment.
One more note. I tested this solution in facebook. Skype doesn't seem to accept that and, again, the posts I checked saying that Skype doesn't handle Intent properly (not sure what that means).
Now to solution. In main activity for example in OnCreate method add the follow.
ReturnImagePage is the name of my page class where I select an image
Xamarin.Forms.MessagingCenter.Subscribe<ReturnImagePage, string>(this, "imageUri", (sender, requestedUri) => {
Intent share = new Intent();
string uri = "file://" + requestedUri;
share.SetData(Android.Net.Uri.Parse(uri));
// OR
//Android.Net.Uri uri = Android.Net.Uri.Parse(requestedUri);
//Intent share = new Intent(Intent.ActionSend);
//share.PutExtra(Intent.ExtraStream, uri);
//share.SetType("image/*");
//share.AddFlags(ActivityFlags.GrantReadUriPermission);
SetResult(Result.Ok, share);
Finish();
});
Above will listen for the message when the image is selected.
Then in XFroms code when image is selected dowload it, store it, get path and send to Activity using it's path. Below is my test path
MessagingCenter.Send<ReturnImagePage, string>(this, "imageUri", "/storage/emulated/0/Android/data/ButtonRendererDemo.Droid/files/Pictures/temp/IMG_20170207_174559_21.jpg");
You can use static public class to save and access results like:
public static class StaticClass
{
public static int Result;
}

App.OnResume error in Xamarin forms on Android and IOS devices

We are using xamarin forms. After an Android or IOS device resumes from background, we are making a REST call in .net that is being triggered by a timer. The first attempt on IOS returns a "The Descriptor is not a socket" error and the Android returns a "Connection refused" error. The same code works fine in Windows. Future attempts (every few seconds) in all 3 platforms work fine. Has anyone seen this and have a fix?
Code
//app on resume event
protected async override void OnResume()
{
// Handle when your app resumes
if (MainPage is RootPage)
{
RootPage mainPage = MainPage as RootPage;
if (mainPage.Detail is NavigationPage)
{
NavigationPage nvPage = mainPage.Detail as NavigationPage;
if(nvPage.CurrentPage is ThingsPage)
{
ThingsPage thPage = nvPage.CurrentPage as ThingsPage;
thPage.TurnOnTimer();
}
}
}
}
//code on the page
public void TurnOnTimer()
{
if (viewModel != null)
{
viewModel.ContinueTimer = true;
viewModel.StartAnotherTimer();
}
}
//code in view model
public async void StartAnotherTimer()
{
while (ContinueTimer)
{
try
{
DevicesUpdate devicesUpdate = await DataSource.GetDevices(LocationID, ControllerID, lastDevicesUpdateReceivedAt);
}
catch (Exception ex)
{
}
// Update the UI (because of async/await magic, this is still in the UI thread!)
if (ContinueTimer)
{
await Task.Delay(TimeSpan.FromSeconds(3));
}
}
}
public static async Task<DevicesUpdate> GetDevices(Guid locationID, Guid controllerID, DateTime lastUpdateReceivedAt)
{
DevicesUpdate devicesUpdate = await GetLastUpdatedDevices(controllerID, lastUpdateReceivedAt);
}
//code in view model
public static async Task<DevicesUpdate> GetLastUpdatedDevices(Guid controllerID,
DateTime lastUpdate)
{
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
string url = string.Format("http://appname.azurewebsites.net/api/devices?controllerid={1}&lastUpdate={2}"
, Constants.WebServerURL, controllerID, lastUpdate);
System.Net.Http.HttpResponseMessage response = await client.GetAsync(new Uri(url));
string result = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
DevicesUpdate devices = JSONHelper.Deserialize<DevicesUpdate>(result);
return devices;
}
else
{
if (response.ReasonPhrase == "UserException")
{
throw new UserException(result);
}
else
{
//throw error because the response from rest api is not a success
throw new System.Net.Http.HttpRequestException(result);
}
}
}
You might have a few things happening here that's causing problems.
GetDevices doesn't return anything. (I hope you just left out the return for brevity sake)
You are never setting ContinueTimer to false.
What iOS version are you on? In later versions, you HAVE to use HTTPS or explicitly allow non-secure connections. This shouldn't be a problem because Azure has ssl.
If you plan on running this in the background, you need to register your app as a background process.
If you don't plan on running this in the background, you might have issues with previous attempts being ran (or still trying to execute, or just have failed) and then calling more.
What is the reason for calling the 3 second timer for the network calls? What if the call takes more than 3 seconds (then you are making duplicate calls even though the first might succeed).
If you want to make your network calls more robust, check out this Blog Post by Rob Gibbons about resilient network calls.
First thing I would do is remove it from the timer because it seems like the underlying sockets are having issues cross-thread.

Launch Cordova Windows App with Parameters

Am finding hard to launch Cordova Windows App, from another native Windows App.
Using Protocol invocation, I am passing few parameters to Cordova Windows App, to see if the Cordova app identifies those parameters from the Windows Native App.
Is there anyway to pass Parameters from native Windows App to Cordova App, so that Cordova App identifies the parameters as arguments?
In native windows 8 store app I am using app protocol association to send parameters one app to another app. like
in sender app:
mainpage.xaml.cs on button click
var url = "apptest:?" + name;
Uri uri = new Uri(url);
await Launcher.LaunchUriAsync(uri);
in received app
Package.appxmanifest:
Declarations --> available declarations add --> protocol --> name = apptest
app.xaml.cs
protected override void OnActivated(IActivatedEventArgs args)
{
if (args.Kind == ActivationKind.Protocol)
{
ProtocolActivatedEventArgs protocolArgs = args as ProtocolActivatedEventArgs;
var rootFrame = new Frame();
rootFrame.Navigate(typeof(MainPage), protocolArgs);
Window.Current.Content = rootFrame;
}
Window.Current.Activate();
}
mainpage.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ProtocolActivatedEventArgs pa = e.Parameter as ProtocolActivatedEventArgs;
if(pa != null)enter code here
{
string qS = pa.Uri.Query;
if (!string.IsNullOrEmpty(qS))
{
Txt_name.Text = qS;
}
}
}
in this way i will take the data from sender app.
Same like is there any way to receive data from windows 10 native app to cordova app. it is very hard to find the solution. not able to find the exact piece of code.

Fail to attach windows service with Skype4COM to Skype Client

I tried to create a windows service which will allow to interact with Skype Client.
I'm using SKYPE4COM.DLL lib.
When I create a simple console or win32 aplication all works ok (I have the Skype request for this application and it works well). But when I try to run this application as a service,
I have an error
Service cannot be started. System.Runtime.InteropServices.COMException (0x80040201): Wait timeout.
at SKYPE4COMLib.SkypeClass.Attach(Int32 Protocol, Boolean Wait)
at Commander.Commander.OnStart(String[] args)
at System.ServiceProcess.ServiceBase.ServiceQueuedMainCallback(Object state)
And I have no notification about process connecting to Skype.
Can you give me an advice how to attach service to Skype client or maybe I need to change my Skype settings?
I think it is not possible due to Windows User Id security restrictions. You have to run your application under the same user as Skype otherwise it won't be able to attach.
I had the same issue.
Resolved it by converting it to Windows Application and using it as System Tray App:
[STAThread]
static void Main()
{
Log.Info("starting app");
//facade that contains all code for my app
var facade = new MyAppFacade();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using (ProcessIcon icon = new ProcessIcon(facade))
{
icon.Display();
Application.Run();
}
}
public class ProcessIcon : IDisposable
{
private readonly MyAppFacade facade;
private NotifyIcon ni;
public ProcessIcon(MyAppFacade facade)
{
this.facade = facade;
this.ni = new NotifyIcon();
}
public void Display()
{
ni.Icon = Resources.Resources.TrayIcon;
ni.Text = "Skype soccer";
ni.Visible = true;
// Attach a context menu.
ni.ContextMenuStrip = new ContextMenuStrip();
var start = new ToolStripMenuItem("Start");
start.Click += (sender, args) => facade.Start();
ni.ContextMenuStrip.Items.Add(start);
var stop = new ToolStripMenuItem("Stop");
stop.Click += (sender, args) => facade.Stop();
ni.ContextMenuStrip.Items.Add(stop);
var exit = new ToolStripMenuItem("Exit");
exit.Click += (sender, args) => Application.Exit();
ni.ContextMenuStrip.Items.Add(exit);
}
public void Dispose()
{
ni.Dispose();
}
}

Resources