Warning: View is not in Window Hierarchy in Xamarin.Forms - ios

I am doing a Xamarin project, Forms and I have integrated Xam.Plugins.Messaging to send SMS from my app. For this I have created a custom renderer in my iOS project with below code:
AppDelegate smsObj = new AppDelegate();
bool a= smsObj.ShowAndSendSMS(new string[] { "123" }, "Hi there");
And in my AppDelegate, I have the code as below:
public bool ShowAndSendSMS(string[] recipients, string body)
{
UIViewController sms = new UIViewController();
if (MFMessageComposeViewController.CanSendText)
{
MFMessageComposeViewController message = new MFMessageComposeViewController();
message.Finished += (sender, e) => {
message.DismissViewController(true, null);
};
message.Body = body;
message.Recipients = recipients;
sms.PresentModalViewController(message, false);
}
return true;
}
The problem I am facing is on my first-time app launch, the functionality to share SMS doesn't work and the debug log gives me warning like "Attempt to present on whose view is not in the window hierarchy!"
However, if I restart the app, the same functionality works like a charm. Any ideas from where i have made mistake?

I think the problem is with the fact that you're newing up an AppDelegate and calling the ShowAndSendSMS from there. iOS is going to new up that AppDelegate for you upon app startup, and you should always use that, as opposed to creating a new instance of AppDelegate (at least I've never seen a situation that called for a multi-AppDelegate-instance pattern). So, try this:
Create a helper class in your project like this (I don't really like the word "helper", but that's beside the point; name it something fitting for your project):
using Foundation;
using UIKit;
public class SmsHelper
{
public bool ShowAndSendSMS(string[] recipients, string body)
{
if (MFMessageComposeViewController.CanSendText)
{
UIViewController sms = new UIViewController();
MFMessageComposeViewController message = new MFMessageComposeViewController();
message.Finished += (sender, e) => {
message.DismissViewController(true, null);
};
message.Body = body;
message.Recipients = recipients;
sms.PresentModalViewController(message, false);
}
return true;
}
}
And then change your page renderer to consume it like this:
public class SMS_Ios: PageRenderer
{
private readonly TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
SmsHelper smsObj = new SmsHelper();
bool a = smsObj.ShowAndSendSMS(new string[] {"123"}, "Hi there");
}
}
And finally, remove ShowAndSendSMS from your AppDelegate.cs, since you'll be using your SMS helper going forward.
Let me know if that works for you.

If you have already installed the Xam.Plugins.Messaging package in the PCL and your platforms. You can just use the API from it in PCL to implement that without any special codes in your iOS platform.
You can just use the APIs of Xam.Plugins.Messaging in the PCL, like this:
// Send Sms
var smsMessenger = CrossMessaging.Current.SmsMessenger;
if (smsMessenger.CanSendSms)
smsMessenger.SendSms("+27213894839493", "Well hello there from Xam.Messaging.Plugin");
Reference: Messaging Plugin for Xamarin and Windows.

Related

ICloudStorage.GetChildren() never return

I'm trying to access Google Drive with CloudRail using the following codes.
// Actual string value removed.
private const string GDRIVE_CLIENT_ID = "client_id";
private const string ANDROID_PACKAGE_NAME = "package_name";
private const string CLOUDRAIL_APP_KEY = "app_key";
private readonly string REDIRECT_URL = $"{ANDROID_PACKAGE_NAME}:/oauth2redirect";
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set CloudRail application key.
CloudRail.AppKey = CLOUDRAIL_APP_KEY;
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
try
{
var googleDrive = new GoogleDrive(this, GDRIVE_CLIENT_ID, "", REDIRECT_URL, "state");
googleDrive.UseAdvancedAuthentication();
m_Service = googleDrive;
Action act = () =>
{
var list = m_Service.GetChildren(#"/");
// The function call never return.
};
var thread = new Thread(new ThreadStart(act));
thread.Start();
}
catch (Exception ex)
{
}
}
After calling into ICloudStorage.GetChildren(), my apps get redirected to login into Google account. After user has logged in to Google account and granted consent to the application, the function call never return. No exception is caught either.
What could have go wrong?
I got a reply from CloudRail support team and manage to solved the issue with their help.
You need to include the IntentFilter and LaunchMode to SingleTask on top of your activity.
Also you need to put OnNewIntent method as shown below:
[Activity(Label = "Awesome.Android", LaunchMode = Android.Content.PM.LaunchMode.SingleTask)]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataScheme = "Android_Package_Name")]
public class MainActivity : Activity
{
protected override void OnNewIntent(Intent intent)
{
CloudRail.AuthenticationResponse = intent;
base.OnNewIntent(Intent);
}
}
The key is that the corresponding Activity class (in my case, MainActivity) has to be decorated with IntentFilter. Also, OnNewItent has to be overridden and passing the response back to CloudRail.AuthenticationResponse in the override.
I also found that android package name must be in full lower case, otherwise it won't work either.
Edit [2018.07.19]
Android package name must contain at least one period character (.), otherwise, you may encounter invalid_request - missing authority error.

Xamarin IOS Facebook SDK LoginButton

Im new to Xamarin and IOS development and dont understand why I get this following error:
But first some Information:
Im using this SDK:
https://components.xamarin.com/view/facebookios
and creating my LoginButtin in my ViewController in ViewDidLoad like this:
loginButton = new LoginButton(new CGRect(48, 0, 218, 46))
{
LoginBehavior = LoginBehavior.Native,
ReadPermissions = readPermissions.ToArray()
};
View.AddSubview(button);
But in my Storyboard I get this error message:
Edit: my ViewController.cs
using System;
using System.Collections.Generic;
using UIKit;
namespace FacebookLogin
{
public partial class ViewController : UIViewController
{
List<string> readPermissions = new List<string> { "public_profile" };
LoginButton loginButton;
public ViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
// Perform any additional setup after loading the view, typically from a nib.
loginButton = new LoginButton(new CGRect(48, 0, 218, 46))
{
LoginBehavior = LoginBehavior.Native,
ReadPermissions = readPermissions.ToArray()
};
View.AddSubview(button);
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
// Release any cached data, images, etc that aren't in use.
}
}
}
Make sure to follow the instructions found on the getting started page. I see an empty component in your solution explorer but just in case, make sure that you installed Xamarin.Facebook.iOS 4.27.1 with nugget. Of course, you also need to set up your facebook app, login, and configure the iOS portion (like setting the BundleID).
Don't create the button in the controller. What you can do is use the storyBoard designer to drop in a regular button. Then, in the properties window click on Class and it should open a dropdown menu. In the selection you should see FBSDKLoginButton, select that class. Give it a name like btnFacebook.
In the codebehind for your controller it will look like this:
string[] readPermissions = { "public_profile" };
public override void ViewDidLoad()
{
base.ViewDidLoad();
btn.LoginBehavior = LoginBehavior.Native;
btnFacebook.ReadPermissions = readPermissions;
// Handle actions once the user is logged in
btnFacebook.Completed += LoginView_Completed;
// Handle actions once the user is logged out
btnFacebook.LoggedOut += LoginView_LoggedOut;
}
private void LoginView_Completed(object sender, LoginButtonCompletedEventArgs e)
{
if (e.Error != null)
{
return;
}
if (e.Result.IsCancelled)
{
return;
}
}
private void LoginView_LoggedOut(object sender, EventArgs e)
{
}
For good measure, clean solution and recompile. The login button won't appear as a facebook login button in your designer but on runtime it will.
As for your error, I don't see anything in your designer so it's curious that it's giving you an error like that. Open the Document Outline (View -> Other Windows -> Document Outline) and see if there's any invisible garbage (elements that aren't being rendered) that has to be deleted.
First Of All! You're on the right Way To solve your issue
and Secondly, I would Suggest you that you have created a controller in the above image you need to create a View Controller File and then add a Login Screen Like a Username named Textbox Then a Password named Textbox and A login Button and then View Controller will automatically created once you save the view And Then Finally, You Save the view Controller By Adding the Following Code
partial void login_TouchUpInside(UIButton sender)
{
var auth = new OAuth2Authenticator(clientId: "YOUR_CLIENT_ID", scope: "", authorizeUrl: new Uri("https://m.facebook.com/dialog/oauth/"), redirectUrl: new Uri("http://www.facebook.com/connect/login_success.html"));
auth.Completed += Auth_Completed;
var ui = auth.GetUI();
PresentViewController(ui, true, null);
}
private async void Auth_Completed(object sender, AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var request = new OAuth2Request("POST", new Uri("YOUR Location Where You want to reach After Login"), null, e.Account);
//fb://profile/<id> For opening in Facebook App.
}
DismissViewController(true, null);
}
You See the Above Code And If you Want to Open the Facebook Link In Facebook Application Just Replace URL with
fb://profile/<id>

How to pushasync from detailpage in masterdetailpage?

From the detailpage's view I try to push a new page on and I get this error: **
System.InvalidOperationException: Page must not already have a parent.
I keep trying different things but nothing works. Is there a way to push a page onto it, I mean, the detailpage is a navigationpage but it is a detailpage. Any and all help is much appreciated.
I am using xamarin forms labs ViewFactory.
//app.cs GetMainPage
var rootPage = ViewFactory.CreatePage<HomeVM>();
//in HomeView.xaml.cs, setting the detailpage to the list of messages
Detail = new NavigationPage(ViewFactory.CreatePage<MessagesVM>());
//This is in the MessagesView to show an individual message with a back button to the list of messages
Navigation.PushAsync(ViewFactory.CreatePage<MessageDetailVM>());
If you already have a NavigationPage, do not create another one to wrap your Detail instance in.
Detail = iewFactory.CreatePage<MessagesVM>();
Navigation.PushAsync(ViewFactory.CreatePage<MessageDetailVM>());
On my part, I also having the same error using MessagingCenter, but also solve it by unsubscribing/disposing after page closing/OnDisappearing.
Hope it helps.
public partial class MainPage : MasterDetailPage
{
public MainPage()
{
InitializeComponent();
MasterBehavior = MasterBehavior.Popover;
MessagingCenter.Subscribe<NavigationPage>(this, "Navigate", (pageItem) =>
{
Detail = pageItem;
IsPresented = false;
});
MessagingCenter.Subscribe<string>(this, "Logout", (s) =>
{
Application.Current.MainPage = new LoginPage("", "");
});
}
protected override void OnDisappearing()
{
MessagingCenter.Unsubscribe<NavigationPage>(this, "Navigate");
MessagingCenter.Unsubscribe<string>(this, "Logout");
base.OnDisappearing();
}
}
Unfortunately, I encountered this error again, and I solve it by using this setting the NavigationPage Parent property to null.
MessagingCenter.Subscribe<NavigationPage>(this, "Navigate", (pageItem) =>
{
pageItem.Parent = null; //solution
Detail = pageItem;
IsPresented = false;
});
Both answers before mine are pointing to the right direction, so this is just an addition. The key is in fact to not create the NavigationPage/NavigationView again.
In my project, I am using static objects for the MasterDetailPage, the NavigationView and the corresponding ViewModels in Xamarin Forms App class;
I am only creating an instance if their Value is null, which is likely to happen only if the app was closed before (no matter if closed by the user or the OS). If the app is still running (resumed), I am just using the already existing objects to restore the state.
This solved all these problems for me, and I hope it is helpful for someone else.
Try using the Navigation property of the Detail object like this:
Detail.Navigation.PushAsync(page);
To push a new page on my Detail I use the code below:
Note there I'm using a ListView with page options in MasterDetailPage
private void ListView_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
var item = e.SelectedItem as MainMenuItem;
if (item == null)
return;
var page = (Page)Activator.CreateInstance(item.TargetType);
//Detail = new NavigationPage(page);
Detail.Navigation.PushAsync(page);
IsPresented = false;
MasterPage.ListView.SelectedItem = null;
}

MvvmCross Android Dialog bind programmatically

I want to use the Android.Dialog (Cross.UI) in my MvvmCross project. My first approach was to use AutoViews. As this feature is still fairly young, the alternative was to implement the dialog in touch and Droid platforms.
For now i'm just doing this for Droid and I need to programmatically bind the properties of the ViewModel to the elements of the Dialog.
My View and ViewModel code is the following:
View
public class DialogConfigurationView : MvxBindingDialogActivityView<DialogConfigurationViewModel>
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
DroidResources.Initialise(typeof(Resource.Layout));
Root = new RootElement()
{
new Section("Private Configuration")
{
new EntryElement("Name:"),
new EntryElement("Description:"),
new BooleanElement("Active?")
}
};
}
}
ViewModel
public class DialogConfigurationViewModel : MvxViewModel
{
public ConfigurationSet Configuration
{
get { return _configuration; }
set
{
if (_configuration != value)
{
_configuration = value;
RaisePropertyChanged(() => Configuration);
}
}
}
private ConfigurationSet _configuration;
}
My goal is to have a twoway bind the EntryElement("Name:") with the property ViewModel.Configuration.Name.
Can anyone help me with this? Can this be done?
I don't know if there are any monodroid.dialog mvvmcross samples floating around which don't use autoviews!
However.... the basic syntac for binding should be the same as MonoTouch.Dialog - e.g. something like:
new Section("Contact Info")
{
new StringElement("ID", ViewModel.Customer.ID ?? string.Empty),
new EntryElement("Name", "Name").Bind(this, "{'Value':{'Path':'Customer.Name'}}"),
new EntryElement("Website", "Website").Bind(this, "{'Value':{'Path':'Customer.Website'}}"),
new EntryElement("Primary Phone", "Phone").Bind(this, "{'Value':{'Path':'Customer.PrimaryPhone'}}"),
},
new Section("Primary Address")
{
new EntryElement("Address").Bind(this, "{'Value':{'Path':'Customer.PrimaryAddress.Street1'}}"),
new EntryElement("Address2").Bind(this, "{'Value':{'Path':'Customer.PrimaryAddress.Street2'}}"),
new EntryElement("City").Bind(this, "{'Value':{'Path':'Customer.PrimaryAddress.City'}}"),
new EntryElement("State").Bind(this, "{'Value':{'Path':'Customer.PrimaryAddress.State'}}"),
new EntryElement("Zip").Bind(this, "{'Value':{'Path':'Customer.PrimaryAddress.Zip'}}"),
},
from https://github.com/slodge/MvvmCross/blob/vnext/Sample%20-%20CustomerManagement/CustomerManagement/CustomerManagement.Touch/Views/BaseCustomerEditView.cs
Note that in MvvmCross bindings for MonoTouch and MonoDroid, the default binding for things like text edit boxes is generally TwoWay by default.
If you do get a sample running, then please feel free to post it to a gist or to a repo - or to blog about it - looks like we could do with some samples to work from!

instantiate a MenuItem

I have the following problem with a blackberry demo class:
MenuItem locatorItem = new MenuItem(new StringProvider("Location Search"), 0x230020, 0);
locatorItem.setCommand(new Command(new CommandHandler()
(...)
I am using Eclipse and a BlackBerry simulator to get this demo running and I get the 'Cannot instantiate the type MenuItem' error. I don't know why and there's no suggestion to solve it.
I imported 'net.rim.device.api.ui.MenuItem;'.
I think you're using the wrong type of MenuItem. net.rim.device.api.ui.MenuItem you are using is specific to the Blackberry.
If this is a J2ME Application/Midlet, just create a javax.microedition.lcdui.Command. They are turned into menu items on the blackberry.
If you're also usingnet.rim.device.api.ui.Screen or any other net.rim classes in the application, this is the way menu items are usually created:
function doSomething() {
// Your Code Here
}
// In the function building your screen
MenuItem somethingMi = new MenuItem() {
private MenuItem() { super("Do Something",100001, 5); }
public void run() { doSomething() };
}
addMenuItem(somethingMI);

Resources