I am using the following code (on Xamarin) to log in using the latest Facebook SDK (I am also using Parse to manage my backend):
partial void LoginWithFacebook (UIButton sender)
{
LoginManager login = new LoginManager();
login.LogInWithReadPermissionsAsync(kPermissions).ContinueWith(t => {
if (t.IsFaulted && t.Exception != null) {
Console.Error.WriteLine("Error while authenticating: {0}", t.Exception.Message);
} else {
var result = t.Result;
if (result.IsCancelled) {
Console.Error.WriteLine("User canceled the operation");
} else {
Console.WriteLine("Authenticated!");
ParseFacebookUtils.LogInAsync(result.Token.UserID, result.Token.TokenString, (DateTime)result.Token.ExpirationDate).ContinueWith(loginTask => {
if (!loginTask.IsFaulted) {
InvokeOnMainThread(() => PerformSegue("GoToDashboard", null));
} else {
Console.Error.WriteLine("Could not login to Parse");
}
});
}
}
});
}
And then, to load the friends list (who are also using the App) I use the following code:
if (AccessToken.CurrentAccessToken != null) {
var request = new GraphRequest ("me/friends", null);
request.Start (new GraphRequestHandler ((connection, result, error) => {
if (error != null) {
Console.Error.WriteLine("Error fetching the friends list");
} else {
Console.WriteLine("Result: {0}", result);
}
hud.Hide(true);
}));
}
But the AccessToken always seem to be null even if the authentication is successful. I tried setting it by hand after the authentication but when the App restarts, it is lost again.
EDIT
I have removed the condition "if (AccessToken.CurrentAccessToken != null)" and it makes the request with no problems so I guess I am just using the wrong way to detect if the user is logged in. What's the correct way?
Thanks to the question referenced by #VijayMasiwal I was able to discover the problem. I had forgot to initialize Facebook in the App Delegate.
Here is how the FinishedLaunching method should be implemented when using Facebook:
public override bool FinishedLaunching (UIApplication application, NSDictionary launchOptions)
{
Settings.AppID = kFacebookAppID;
Settings.DisplayName = kFacebookDisplayName;
// I had forgotten this line :)
return ApplicationDelegate.SharedInstance.FinishedLaunching (application, launchOptions);
}
Hope that helps.
Related
In my Xamarin.iOS app, I want to retry an upload after a failure due to http error.
But I'm not sure how to implement.
I was thinking I should just keep creating a new task every time it completes with an http error?
Is this the correct way to implement?
NSUrlSessionTaskDelegate:
public async override void DidCompleteWithError(NSUrlSession session, NSUrlSessionTask task, NSError error)
{
if (task.Response != null)
{
if (task.Response is NSHttpUrlResponse httpResponse)
{
if (httpResponse.StatusCode == 200)
{
//some clean up logic here
}
else if (httpResponse.StatusCode > 400 && httpResponse.StatusCode < 500)
{
//what to do to upload again?
}
else
{
var applicationException = new ApplicationException("Server returned 500");
Crashes.TrackError(applicationException);
}
}
}
}
AppDelegate:
public NSUrlSession InitBackgroundSession()
{
string identifier = "com.uba.BookingUploadBackgroundSession";
INSUrlSessionDelegate backgroundUploadSessionDelegate = new BackgroundBookingUploadSessionDelegate();
using (var config = NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration(identifier))
{
config.HttpMaximumConnectionsPerHost = 4;
config.TimeoutIntervalForResource = 300.0; //5mins
return NSUrlSession.FromConfiguration(config, backgroundUploadSessionDelegate, new NSOperationQueue());
}
}
//gets called when user taps Submit from ViewController
public void UploadFile(BookingDTO newBookingDTO)
{
//logic to create NSMutableUrlRequest omitted
var uploadTask = session.CreateUploadTask(request, NSUrl.FromFilename(filename));
uploadTask.TaskDescription = newBookingDTO.ClientId; //we need the booking id so we can delete it later
uploadTask.Resume();
}
I'll accept any answer in C#, Swift or Objective-C
NSURLSessionTask has four different states.As shown in the following image.
When the upload finished.Whether success or fail,the states will become Completed.And the property CurrentRequest will be null. You can only handle the Response of the task.So .I think you can use the same session but have to create a new task.You can use the Notification to send message.
NSUrlSessionTaskDelegate:
public async override void DidCompleteWithError(NSUrlSession session, NSUrlSessionTask task, NSError error)
{
if (task.Response != null)
{
if (task.Response is NSHttpUrlResponse httpResponse)
{
if (httpResponse.StatusCode == 200)
{
//some clean up logic here
}
else if (httpResponse.StatusCode > 400 && httpResponse.StatusCode < 500)
{
//send notification here
NSNotification notification = NSNotification.FromName("UploadFail",null,null);
NSNotificationCenter.DefaultCenter.PostNotification(notification);
}
else
{
var applicationException = new ApplicationException("Server returned 500");
Crashes.TrackError(applicationException);
}
}
}
}
And in AppDelegate.cs , add the notification in method FinishedLaunching
NSNotificationCenter.DefaultCenter.AddObserver(this,new Selector("UploadAgain"),new NSString("UploadFail"),null);
[Export("UploadAgain")]
void UploadAgain()
{
//call your upload code here
}
I am having an issue where either the reminder calendar or event calendar, depending on which displays the popup first, creates multiple calendars and does not save any of the events/reminders. I have the properties set in the plist. Below is the code I am using to request access for the events and reminders:
var calendarEvent = await ANApi.FetchCalendarInfoAsync(AppCore.CurrentAppInfo.AppId);
calendarEvent.Count();
foreach (var calEvent in calendarEvent.ToList())
{
if (calEvent.Type == 1)
{
AppEvent.Current.EventStore.RequestAccess(EKEntityType.Event, (bool granted, NSError err) =>
{
if (granted)
{
lock (_lock)
{
CreateCalendar(EKEntityType.Event);
CreateEvent(calEvent);
}
}
});
}
else
{
AppEvent.Current.EventStore.RequestAccess(EKEntityType.Reminder, (bool remGranted, NSError remErr) =>
{
if (remGranted)
{
lock (_lock)
{
CreateCalendar(EKEntityType.Reminder);
CreateReminder(calEvent);
}
}
});
}
}
The code works fine if I am only receiving events or reminders, but since I am receiving both events and reminders, it is failing to create my calendars properly.
This is how I am creating the calendars:
public void CreateCalendar(EKEntityType type)
{
bool calExists = false;
var appName = NSBundle.MainBundle.ObjectForInfoDictionary("CFBundleDisplayName");
//This returns 0 calendars, depending on which event's request was displayed first.
EKCalendar[] calendars = AppEvent.Current.EventStore.GetCalendars(type);
foreach (EKCalendar cal in calendars)
{
if (type == EKEntityType.Event)
{
if (PersistentLayer.Instance.GetString(Constant.CALENDAR_ID) == null)
calExists = false;
else if (cal.CalendarIdentifier == PersistentLayer.Instance.GetString(Constant.CALENDAR_ID))
calExists = true;
}
else
{
if (PersistentLayer.Instance.GetString(Constant.REMINDER_CALENDAR_ID) == null)
calExists = false;
else if (cal.CalendarIdentifier == PersistentLayer.Instance.GetString(Constant.REMINDER_CALENDAR_ID))
calExists = true;
}
}
//Create a Calendar based on the App's name. If name cannot be found use App Calendar
if (!calExists)
{
EKCalendar calendar = EKCalendar.Create(type, AppEvent.Current.EventStore);
if (appName != null)
calendar.Title = appName.ToString();
else
calendar.Title = "App";
EKSource localSource = null;
foreach (EKSource source in AppEvent.Current.EventStore.Sources)
{
if (source.SourceType == EKSourceType.CalDav)
{
localSource = source;
break;
}
}
if (localSource == null)
return;
calendar.Source = localSource;
calendar.CGColor = new CGColor(255, 255, 0);
NSError calError;
AppEvent.Current.EventStore.SaveCalendar(calendar, true, out calError);
if (calError != null)
{
this.SimpleAlert("Error saving Calender", calError.ToString(), "OK", null);
return;
}
//Store the calendar Id so we can use it when saving events
if (type == EKEntityType.Event)
PersistentLayer.Instance.Edit().PutString(Constant.CALENDAR_ID, calendar.CalendarIdentifier);
else
PersistentLayer.Instance.Edit().PutString(Constant.REMINDER_CALENDAR_ID, calendar.CalendarIdentifier);
}
}
I believe that there is some sort of race condition going on, but I have not been able to figure it out. I tried multiple things to try and get it to work, but I have not had any success.
Thanks
I ended up figuring it out. First, I broke the code up and created all the reminders received followed by the events.
At first, this gave me a Error Domain=EKCADErrorDomain Code=1010 when trying to create the events. The way I solved this was to reset the event store. Once I did this, the Calendars created properly, for both reminders and events, and both events and reminders were added to their respective calendars.
Everything works perfect on android but when I try to get the profile picture on iOS devices. The image returns null. I checked the Facebook documentation for iOS 9 I have exactly the same plist as shown in documentation. When I run the app in console I see "FB is log in" message but the profile pic has not shown. Can anyone help?
void Awake()
{
instance = this;
FB.Init(SetInıt, OnHideUnity);
}
public void FbLogin()
{
// This is an event trigger when the button pressed.
List<string> permissions = new List<string>();
permissions.Add("public_profile");
FB.LogInWithReadPermissions(permissions, AuthcallBack);
}
void DealWithFbMenus(bool isLoggedIn)
{
// This function is called in SetInit func in Awake.
if(isLoggedIn)
{
fbButton.SetActive(false);
profilePicture.gameObject.SetActive(true);
loggedInPlayer = true;
//FB.API("/me?fields=first_name", HttpMethod.GET, DisplayUserName);
FB.API("/me/picture?type=square&height=128&width=128", HttpMethod.GET, DisplayProfilePic);
}
else
fbButton.SetActive(true);
}
void DisplayProfilePic(IGraphResult result)
{
if(result.Texture != null)
{
profilePicture.sprite = Sprite.Create(result.Texture, new Rect(0,0, 128, 128), new Vector2());
}
}
It is a bug on Unity 5.2. It fixed on new version of Unity 5.3
I am developing an app where I need to be very API request frugal, the less requests the better. The problem is every user has settings and messages and I want to avoid to pull for possible changes on every wake up. And I can't rely on that every user enables push notifications.
My approach is as a compromise to enforce that a user can only be logged in with one device. If they try to login with another device (via facebook) they get an error message where they can choose to either cancel the login or go ahead and logout the other device remotely.
Is this possible?
I found a solution to this problem.
Query number of sessions after login
If the number is greater than 1 ask user what do
logout other device (and go ahead) -> call "deleteAllOtherSessions"
cancel login (and go back to login screen) -> call "deleteLastSession"
Cloud code:
Parse.Cloud.define("getSessionCount", function(request, response) {
if (request.user == null) {
reportError("findSessions", "userCheck", 0);
response.error("invalid user");
return
}
var query = new Parse.Query(Parse.Session);
query.find({
success: function(results) {
response.success(results.length);
},
error: function(error) {
response.error(error);
}
});
});
Parse.Cloud.define("deleteAllOtherSessions", function(request, response) {
if (request.user == null) {
reportError("deleteAllOtherSessions", "userCheck", 0);
response.error("invalid user");
return
}
var sessionToken = request.params.sessionToken;
var query = new Parse.Query(Parse.Session);
// this query will find only sessions owned by the user since
// we are not using the master key
query.find().then(function(results) {
var promises = [];
_.each(results, function (result) {
if(result.get("sessionToken") != sessionToken) {
promises.push(result.destroy());
}
});
return Parse.Promise.when(promises);
}).then(function() {
response.success(true);
},
function(error) {
response.error(error)
});
});
Parse.Cloud.define("deleteLastSession", function(request, response) {
if (request.user == null) {
reportError("deleteLastSession", "userCheck", 0);
response.error("invalid user");
return
}
var sessionToken = request.params.sessionToken;
var query = new Parse.Query(Parse.Session);
query.descending("createdAt");
// this query will find only sessions owned by the user since
// we are not using the master key
query.find().then(function(results) {
var promises = [];
console.log(results);
promises.push(results[0].destroy());
return Parse.Promise.when(promises);
}).then(function() {
response.success(true);
},
function(error) {
response.error(error)
});
});
Hope that helps somebody.
Basically I am trying to connect to Facebook using actionscript 3.0. If I were to run the application in Facebook, and it is connected to Facebook, the stamp image would be added to the screen. The codes below are the functions used:
private var facebookAppID:String = "myappID";
private var fbLoggedIn:Boolean = false;
public function tryout() {
Facebook.init(facebookAppID, onInit);
FBConnect();
}
protected function onInit(result:Object, fail:Object):void {
if (result) { //already logged in because of existing session
fbLoggedIn = true;
} else {
fbLoggedIn = false;
}
}
public function FBConnect():void {
trace("in FBConnect");
if (fbLoggedIn)
{
showFbForm();
trace("success logged in");
}
else
{ // attempt to request for login
var opts:Object = {scope:"publish_stream, email"};
Facebook.login(onLogin, opts);
trace("failed logged in");
}
}
protected function onLogin(result:Object, fail:Object):void {
trace("in onLogin");
if (result) { //successfully logged in
fbLoggedIn = true;
showFbForm();
} else {
fbLoggedIn = false;
return;
}
}
protected function showFbForm():void {
addChild(stamp1);
stamp1.x = 0;
stamp1.y = 0;
trace("in showFBForm()");
}
The stamp1 should be displayed on the stage. However, nothing is displayed at all. I have been trying and researched but it still does not display.
I don't know if you're hiding your app ID or you just filled in MyAppID as your appID,
but if it's the second then it won't work because that appID does not exist. You need to find your appID in your facebook dashboard.
Since your code can't connect to the appID, it won't show any content because the content of "myAppID" is null.