I am attempting to create a C++ bridge for the IOS facebook SDK. I can log in fine, but when I try to retrieve the user details the graph functions never seem to execute though I can step over them in the debugger. Here's what I have:
void facebook_bridge::performLoginWithFacebook()
{
// Open a session showing the user the login UI
// You must ALWAYS ask for basic_info permissions when opening a session
[FBSession openActiveSessionWithReadPermissions:#[#"basic_info", #"email"]
allowLoginUI:YES
completionHandler:
^(FBSession *session, FBSessionState state, NSError *error) {
// Retrieve the app delegate
AppController* appDelegate = (AppController*)[UIApplication sharedApplication].delegate;
// Call the app delegate's sessionStateChanged:state:error method to handle session state changes
[appDelegate sessionStateChanged:session state:state error:error];
}];
isLoggedIn = true;
}
cocos2d::CCDictionary* facebook_bridge::getUserDetails()
{
__block NSDictionary *currentPermissions;
// Request the permissions the user currently has
[FBRequestConnection startWithGraphPath:#"/me/permissions"
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (!error){
// These are the current permissions the user has
currentPermissions= [(NSArray *)[result data] objectAtIndex:0];
NSLog(#"No error");
// We will store here the missing permissions that we will have to request
// If we have permissions to request
} else {
NSLog([NSString stringWithFormat:#"error %#", error.description]);
}
}];
cocos2d::CCDictionary* dict = cocos2d::CCDictionary::create();
for(int i = 0; i < currentPermissions.count; i++) //get a bad access here, currentPermissions is always null
{
NSString* val = currentPermissions.allValues[i];
NSString* k = currentPermissions.allKeys[i];
std::string value = *new std::string(val.UTF8String);
std::string key = *new std::string(k.UTF8String);
cocos2d::CCString *ccVal = cocos2d::CCStringMake(value);
dict->setObject((cocos2d::CCObject*)ccVal, key);
}
dict->retain();
return dict;
}
I think the FBRequest is asynchronous, so I tried placing it in the first function to run on the same thread, to no avail.
Any help? (I'm very new to objective C, but have experience in most other languages)
I think the FBRequest is asynchronous, so I tried placing it in the
first function to run on the same thread, to no avail.
I think you are completely missing the meaning of "asynchronous". You don't have to have threads for things to be asynchronous -- you can execute something asynchronously on the same thread. (Though in this case it probably does its main stuff on another thread, and then runs the completion block on the original thread.)
When you call an asynchronous API, the completion block will not execute before the rest of your function. Pretty much guaranteed.
Related
My objective
To exchange refresh token for access token from google using OAuth 2.
My code
bool Google_Account::Refresh_Access_Token_Using_Refresh_Token()
{
// Prepare Url
QUrl url(tr("https://www.googleapis.com/oauth2/v4/token"));
// Create request
QNetworkRequest request(url);
request.setRawHeader("Host:","www.googleapis.com");
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
// Create request body ClientID, ClientSecret, RefreshTokenString are class data members
QString RequestBody = tr("client_secret=%1&").arg(ClientSecret) +
tr("grant_type=refresh_token&")+
tr("refresh_token=%1&").arg(RefreshTokenString)+
tr("client_id=%1").arg(ClientID);
QByteArray array = RequestBody.toUtf8();
// Get reply
QNetworkReply *reply = mQNAM.post(request, array); // mQNAM is QNetworkAccessManager
// Set timeout to reply while waiting for reply finished
bool stop = false;
QTimer timer;
timer.setSingleShot(true);
QObject::connect(&timer, &QTimer::timeout, [&](){
qDebug()<<"Time out";
stop = true;
});
timer.start(5000);
// Wait till the response is completed
while(!reply->isFinished()){
QCoreApplication::processEvents();
if(stop){
qDebug()<<"Going to abort";
reply->abort();
}
}
// Check for reply
if(reply->isFinished()){
if(reply->error() != QNetworkReply::NoError){
qDebug()<<reply->readAll();
emit setMessage("Error: "+reply->errorString());
delete reply;
return false;
}
else{
QByteArray array = reply->readAll();
QJsonDocument document = QJsonDocument::fromJson(array);
QJsonObject obj = document.object();
access_token = obj.value("access_token").toString(); //access_token is class data variable
delete reply;
return true;
}
}
else{
delete reply;
return false;
}
}
The problem is that if I run this code in my windows 7 pc(Qt 5.11.1) everything is fine I get the access token but if i run in my raspberry pi(raspbian Qt 5.7) I get Error 400, Bad request from google. I tried using the access_token got from my windows pc and made other request such as to get the file list from google drive, they are working fine in raspberry, but only this I am having problem. What am I doing wrong?
P.S the code is refactored to the specific details only, in reality I am getting the client id and other keys from QSettings
This smells like you're getting a 400 code when Qt is requesting a token, because the login code you get when the flow returns to your app is URL-encoded. We wrote about How To Authenticate with Google SSO in Qt with some code samples including this bit that injects a parameter modifier in the flow:
google->setModifyParametersFunction([](QAbstractOAuth::Stage stage, QVariantMap* parameters) {
// Percent-decode the "code" parameter so Google can match it
if (stage == QAbstractOAuth::Stage::RequestingAccessToken) {
QByteArray code = parameters->value("code").toByteArray();
(*parameters)["code"] = QUrl::fromPercentEncoding(code);
}
});
Do that before the initial request, but as suggested by #Giancarlo above, I'd rewrite your code to work asynchronously. It's simpler, and more reliable.
I have an app with contextual commands. After triggered a contextual command, it will make a HTTP request with a link and post the result on the card, something like, "Completed!". I want this card to be closed by itself after one second so that the user need not to tap to close it. Once the result card is closed, it will go back to contextual command lists with "Ok, glass" at footer and ready for next command.
May i know how to do that?
private class HTTPRequest extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... arg0) {
try {
if (mWhat.equalsIgnoreCase("GET")) {
// get json via YouTube API
URL url = new URL("http://example.com");
mUrlConnection = (HttpURLConnection)
url.openConnection();
InputStream in = new BufferedInputStream(
mUrlConnection.getInputStream());
int ch;
StringBuffer b = new StringBuffer();
while ((ch = in.read()) != -1) {
b.append((char) ch);
}
mResult = new String(b);
}
} catch (Exception e) {}
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
mTvInfo.setText(mResult);
}
You can use an Android Dialog for this:
Use CardBuilder to create the "Completed" card using the MENU layout.
Create a new instance of Dialog and set its content view to be the view returned by CardBuilder.getView.
Show the dialog.
Use Handler.postDelayed (or some similar mechanism) to automatically dismiss the dialog after the desired amount of time has passed.
How do I start a fragment in my Android application from a notification in the notification bar?
I've tried to implement this answer of creating my own action and then setting the action to the intent, but I'm unsure how to use it and what is required additionally - like adding something to the Manifest.
I've got a notification class that receives a context, a message and then an action. I then want to filter on that action to determine which fragment to launch, but I don't know how to launch a fragment as opposed to launching an activity.
Here is my Notifications.java class (incomplete):
public class Notifications {
private Context mContext;
public Notifications(Context context) {
this.mContext = context;
}
public static void notify(Context context, String message, String action) {
//Action you invent should include the application package as a prefix — for example: "com.example.project.SHOW_COLOR".
action = "my.package.name.here.frag."+action;
//Construct a user message.
String appName = context.getResources().getString(R.string.app_name);
// Use the Notification manager to send notification
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// Create a notification using android stat_notify_chat icon.
Notification notification = new Notification(R.drawable.ic_stat_notification, message, 0);
//Sound, lights, vibration.
//REMEMBER PERMISSIONS.
notification.defaults |= Notification.DEFAULT_SOUND;
notification.defaults |= Notification.DEFAULT_VIBRATE;
notification.defaults |= Notification.DEFAULT_LIGHTS;
// Create a pending intent to open the application when the notification is clicked.
//Restart the app.
Intent launchIntent = null;
//Get the action and based on what the action is, launch the application displaying the appropriate fragment.
if (action.equalsIgnoreCase("friend")){
//New friend notification
//Launch application displaying the list of friends
}
if (action.equalsIgnoreCase("article")){
//New article has been posted
//Launch application displaying the news feed fragment
}
if (action.equalsIgnoreCase("points")){
//Points scored notification
//Launch application displaying the user's profile
}
if (action.equalsIgnoreCase("redeemable")){
//New redeemable is offered
//Launch application displaying the list of redeemables
}
if (!action.equalsIgnoreCase("friend")
&& !action.equalsIgnoreCase("article")
&& !action.equalsIgnoreCase("points")
&& !action.equalsIgnoreCase("redeemable")){
//Not specific, so launch the application from scratch displaying the activity feed
launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
}
if(action != null && launchIntent != null){
launchIntent.setAction(action);
}
// Set the notification and register the pending intent to it
notification.setLatestEventInfo(context, appName, message, pendingIntent);
// Trigger the notification
notificationManager.notify(0, notification);
}
}
So this was actually pretty easy. Hopefully I can help someone else see this too.
I send an action to this notify function. The I add that action to my intent to launch an activity. In my case I open the launching activity, because all the fragments are loaded from within that activity based on what the user does. So I set the action using setAction and the I use that action in the activity as below.
My Notifications.java class changed to this:
public static void notify(Context context, String message, String action) {
action = action.toUpperCase();
// Create a pending intent to open the the application when the notification is clicked.
//Restart the app.
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
if(action != null && launchIntent != null){
launchIntent.setAction(action);
}
PendingIntent pendingIntent = PendingIntent.getActivity(context, -1, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT);
notification.when = System.currentTimeMillis();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
// Set the notification and register the pending intent to it
notification.setLatestEventInfo(context, appName, message, pendingIntent);
// Trigger the notification
notificationManager.notify(0, notification);
}
And then in my activity from where I load the fragments, I get the action and filter it:
Intent intent = getIntent();
try{
String action = intent.getAction().toUpperCase();
if(action != null){
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_friend))){
goFrag(getResources().getInteger(R.integer.FRAG_A_INT));
}
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_article))){
goFrag(getResources().getInteger(R.integer.FRAG_B_INT));
}
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_points))){
goFrag(getResources().getInteger(R.integer.FRAG_C_INT));
}
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_redeemable))){
goFrag(getResources().getInteger(R.integer.FRAG_D_INT));
}
if(action.equalsIgnoreCase(getResources().getString(R.string.notification_action_dance))){
goFrag(getResources().getInteger(R.integer.FRAG_E_INT));
}
}else{
Log.d(TAG, "Intent was null");
}
}catch(Exception e){
Log.e(TAG, "Problem consuming action from intent", e);
}
In my goFrag function I replace the fragment if the required fragment is still in memory (meaning the user was there earlier and it hasn't been destroyed yet), or I create a new fragment required.
I have a Silverlight 5.0 application and prvoide MS Word Automation functionality where the user can edit/add new document. I have gone through to MSDN pages but couldn't find any event that MS Word triggers after saving the document. The only event that talks about saving is the "DocumentBeforeSave" event that dosen't helps. I need to know when the MS Word finishes saving the document so that is it ready to save to server.
Can someone help me with this?
Any idea is very much approciated.
using MSWord = Microsoft.Office.Interop.Word;
namespace ConsoleApplication4
{
class Program
{
static void Main()
{
var app = new MSWord.Application();
var doc = app.Documents.Open(#"..\..\myDoc.docx");
app.DocumentBeforeSave += app_DocumentBeforeSave;
}
static void app_DocumentBeforeSave(MSWord.Document Doc, ref bool SaveAsUI, ref bool Cancel)
{
app.DocumentBeforeSave -= app_DocumentBeforeSave;
Cancel = true;
Doc.Save();
if(Doc.Saved){
//Now you know the document has saved
}
app.DocumentBeforeSave += app_DocumentBeforeSave;
}
}
}
The Save method doesn't run on a seperate thread, so it only returns once the Save is complete.
The DocumentBeforeSave event takes in a boolean call Cancel this is passed with the ref parameter, and setting it to true cancels the save that is about to take place.
You could set this to true, and then call Save yourself, this way you will know when the save has completed, as it runs on the same thread, something like this:
using MSWord = Microsoft.Office.Interop.Word;
namespace ConsoleApplication41
{
class Program
{
static void Main()
{
var app = new MSWord.Application();
var doc = app.Documents.Open(#"..\..\myDoc.docx");
app.DocumentBeforeSave += app_DocumentBeforeSave;
}
static void app_DocumentBeforeSave(MSWord.Document Doc, ref bool SaveAsUI, ref bool Cancel)
{
Cancel = true;
Doc.Save();
//Now you know the document has saved
}
}
}
I'm currently facing a strange problem in Qt.
I have an application that handles multiple clients using the QTcpSocket.
The clients provide the server with login details and the server is doing a check on the MySQL database to verify the credentials on a separate thread.
I have 2 different client programs that log-in to this server
1. A simple client using Qt to connect there
2. A client that is coded in C-Sharp (.net)
However
When the second client tries to log-in to the server, i get an error on the server telling me this:
QObject: Cannot create children for a parent that is in a different thread. Parent is QNativeSocketEngine(0x1231e00), parent's thread is
QThread(0xc625f0), current thread is CMySQLHandler(0xc67188)
I'm guessing this is happening on the MySQL handler thread
But the weird part is, it is not happening at all on the client that is coded in Qt
here is the code:
void CMySQLHandler::onAuthRequest(ulong clientID, QString email, QString psw, QString HWID, QString ip)
{
string pass = "", salt = "", name = "", avatar = "";
uint UserID = 0;
CUserData *extra = new CUserData();
bool success = false;
QString failReason = "Success";
string Query = "SELECT password, username, avatar, userid FROM jos_users LEFT JOIN jos_community_users ON jos_users.id = jos_community_users.userid WHERE email = '"+email.toStdString()+"'";
SQLRow Row = GetRow(Query);
if(!Row.empty())
{
QString hashedPass = QString(Row[0].c_str());
pass = hashedPass.split(':').at(0).toStdString();
salt = hashedPass.split(':').at(1).toStdString();
name = Row[1];
avatar = Row[2];
UserID = QString(Row[3].c_str()).toUInt();
QByteArray data;
data.append((psw + QString(salt.c_str())));
QString blah = QString(QCryptographicHash::hash(data,QCryptographicHash::Md5).toHex());
if(QString(pass.c_str()) == blah )
success = true;
else
failReason = "Invalid password";
}
else
{
failReason = "E-Mail not found";
emit Handle->onAuthCompleted(clientID, false, failReason, extra);
return;
}
extra->Avatar += avatar.c_str();
extra->UserID = UserID;
extra->Username = QString(name.c_str());
emit Handle->onAuthCompleted(clientID, true, QString(), extra);
}
here's the signal definitions
void CMySQLHandler :: onTimerInit()
{
//Authentication
connect(this, SIGNAL(doAuthCompleted(ulong,bool,QString,CUserData*)), Handle, SLOT(onAuthCompleted(ulong,bool,QString,CUserData*)));
connect(Handle, SIGNAL(doRequestAuth(ulong,QString,QString,QString,QString)), this, SLOT(onAuthRequest(ulong,QString,QString,QString,QString)));
//end
CMySQL::Init(Handle);
}
I am new to the Qt framework.
Please excuse the lack of knowledge.
Thank you for your time reading this.
Qt uses an concept of ownership of objects by threads. Thread own objects to do the signal/slot mechanism. The problem is that you are trying to create an new object in Thread CMySQLHandler(0xc67188) and assign it the parent QNativeSocketEngine(0x1231e00). But QNativeSocketEngine is owned by QThread(0xc625f0) which is not allowed. I can't see the error in your code so you may need to do some more debugging to find the line that cause the error and edit your question.
This kind of different-thread problems happen all the time with Qt when you use multiple threads. The canonical way to solve this problem is to use signals and slots.
The error doesn't happen in onAuthRequest, it is probably earlier, where you try to call this function. Maybe you are creating a CMySQLHandler object in a one thread, and try to execute member functions of it in another.
If you for some reason need to call a function of a object which belongs to a different threat, create a slot in the object which wraps the function and call it with an emit.
See the similar question QObject: Cannot create children for a parent that is in a different thread