I'm looking to pass a Facebook session across activities. I saw the example from Facebook's SDK and someone mentioned that the "Simple" example has a way to do this: https://github.com/facebook/facebook-android-sdk/blob/master/examples/simple/src/com/facebook/android/SessionStore.java
But how does this work? In my MainActivity, I have this:
mPrefs = getPreferences(MODE_PRIVATE);
String accessToken = mPrefs.getString("access_token", null);
long expires = mPrefs.getLong("access_expires", 0);
if (accessToken != null) {
//We have a valid session! Yay!
facebook.setAccessToken(accessToken);
}
if (expires != 0) {
//Since we're not expired, we can set the expiration time.
facebook.setAccessExpires(expires);
}
//Are we good to go? If not, call the authentication menu.
if (!facebook.isSessionValid()) {
facebook.authorize(this, new String[] { "email", "publish_stream" }, new DialogListener() {
#Override
public void onComplete(Bundle values) {
}
#Override
public void onFacebookError(FacebookError error) {
}
#Override
public void onError(DialogError e) {
}
#Override
public void onCancel() {
}
});
}
But how do I pass this along to my PhotoActivity activity? Is there an example of this being implemented?
Using SharedPreferences to pass data across activities is not good idea. SharedPreferences used to store some data into memory across application restart or device re-boot.
Instead you have two options:
Declare a static variable to hold facebook session, which is simplest method, but I wont recommend to use Static Fields as far there is no other way.
Make an class implementing parcelable, and set your facebook object there, see an parcelable implementation as follows:
// simple class that just has one member property as an example
public class MyParcelable implements Parcelable {
private int mData;
/* everything below here is for implementing Parcelable */
// 99.9% of the time you can just ignore this
public int describeContents() {
return 0;
}
// write your object's data to the passed-in Parcel
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
// this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods
public static final Parcelable.Creator<MyParcelable> CREATOR = new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
// example constructor that takes a Parcel and gives you an object populated with it's values
private MyParcelable(Parcel in) {
mData = in.readInt();
}
}
For FB SDK 3.5, In my FB login activity, I pass the active session object via intent extras because the Session class implements serializable:
private void onSessionStateChange(Session session, SessionState state, Exception exception) {
if (exception instanceof FacebookOperationCanceledException || exception instanceof FacebookAuthorizationException) {
new AlertDialog.Builder(this).setTitle(R.string.cancelled).setMessage(R.string.permission_not_granted).setPositiveButton(R.string.ok, null).show();
} else {
Session session = Session.getActiveSession();
if ((session != null && session.isOpened())) {
// Kill login activity and go back to main
finish();
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
intent.putExtra("fb_session", session);
startActivity(intent);
}
}
}
From my MainActivity onCreate(), I check for intent extra and initiate the session:
Bundle extras = getIntent().getExtras();
if (extras != null) {
Session.setActiveSession((Session) extras.getSerializable("fb_session"));
}
The example pretty much has the whole implementation. You just use SharedPreferences to store your session. When you need it in your PhotoActivity, just look in SharedPreferences again (perhaps via your SessionStore static methods if you followed the same pattern) to get the Facebook Session that you previously stored.
Related
I'm trying to pass an object containing Analytics Reporting data in an Intent via a broadcast. The problem is the deserialization which returns a LinkedTreeMap instead of the original serialized object, causing a crash with ClassCastException.
I tried to follow quite all answers found here on SO, from using TypeToken to modify ProGuard rules and nothing worked.
I thought to implement Parcelable interface but the problem is that I have an inner private AsyncTask class where the data is collected and pushed into the intent which will be sent via broadcast.
Here is the code of the helper where data is serialized:
public class AnalyticsHelper
{
...
private class GoogleBatchTask extends AsyncTask<GetReportsRequest,Void,GetReportsResponse>
{
#Override
protected GetReportsResponse doInBackground(#NonNull GetReportsRequest... reports)
{
GetReportsResponse response = null;
try {
if (m_reports == null)
return null;
response = m_reports.reports().batchGet(reports[0]).execute();
} catch (IOException e) {
Console.log(e);
}
return response;
}
#Override
protected void onPostExecute(GetReportsResponse response)
{
Intent intent = new Intent();
intent.setAction("com.keyone.contactpackapp.ANALYTICS_DATA");
intent.putExtra("response", new Gson().toJson(response));
Context context = PackConfig.instance().context();
if (context == null)
return;
context.sendBroadcast(intent);
}
}
}
AnalyticsFragment.java, where the deserialization happens:
public class AnalyticsFragment extends Fragment
{
#Override
public void onResume()
{
super.onResume();
// Listen to custom intent with data
IntentFilter filter = new IntentFilter("com.keyone.contactpackapp.ANALYTICS_DATA");
m_receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent)
{
// Get data from intent and pass it to the right fragment
String szJson = intent.getStringExtra("response");
//m_response = new Gson().fromJson(szJson, GetReportsResponse.class);
Type listType = new TypeToken<GetReportsResponse>(){}.getType();
m_response = new Gson().fromJson(szJson, listType);
Fragment fragment = m_activity.currentFragment();
fragment.updateData();
}
};
if (m_activity != null)
m_activity.registerReceiver(m_receiver, filter);
}
}
There was no way to deserialize object in a correct way using Gson neither using Java Serializable interface or Android Parcelable interface due to the nature of the objects to serialize.
So I opted to call an instance of the recipient class and pass object data through a method in it
I tried implementing a signIn method from the OneDrive API, but I am not sure I correctly understood the workflow.
Basically, on first launch of the app, I want to have both the login window and the "authorise the app to..." window". But then, when the user launches the app again, I would like to be directly connected to the app, without any window.
Instead, with the following code, I keep having the second window (where the user decides to accept the app)
#Override
public void signIn() {
//personal code
linkingStarted = true;
signInStatus = SignInStatus.SIGNING_IN;
activity.setUpWait(R.layout.popup_waitgif_white);
//end of personal code
mAuthClient = AuthClientFactory.getAuthClient(activity.getApplication());
if (mAuthClient.getSession().isExpired() && Util.isConnectedToInternet(activity)) {
activity.alertOnUIThread("Login again");
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
mAuthClient.login(activity, SCOPES, mAuthListener);
}
});
} else if (!Util.isConnectedToInternet(activity)) {
activity.alertOnUIThread(activity.getString(R.string.alert_verifyconnection));
} else {
activity.alertOnUIThread("Resigned In OneDrive");
signInStatus = SignInStatus.SIGNED_IN;
mAuthClient.initialize(SCOPES, new AuthListener() {
#Override
public void onAuthComplete(final AuthStatus status, final AuthSession session, final Object userState) {
if (status == AuthStatus.CONNECTED) {
authToken = session.getAccessToken();
oneDriveService = getOneDriveService();
signInStatus = SignInStatus.SIGNED_IN;
} else {
authenticationFailure();
Log.v(TAG, "Problem connecting");
}
}
#Override
public void onAuthError(final AuthException exception, final Object userState) {
//mAuthClient.login(activity, SCOPES, mAuthListener);
}
}, null, authToken);
}
}
and the AuthClientFactory is just this:
public class AuthClientFactory {
private static AuthClient authClient;
private static final String CLIENT_ID = "00000000XXXXX";
public static AuthClient getAuthClient(Context context) {
if (authClient == null)
authClient = new AuthClient(context, OneDriveOAuthConfig.getInstance(), CLIENT_ID);
return authClient;
}
}
You would have an easier time with the OneDrive SDK for Android, as authentication is a much simpler process.
final MSAAuthenticator msaAuthenticator = new MSAAuthenticator() {
#Override
public String getClientId() {
return "<msa-client-id>";
}
#Override
public String[] getScopes() {
return new String[] { "onedrive.appfolder", "wl.offline_access"};
}
}
final IClientConfig oneDriveConfig = new DefaultClientConfig.createWithAuthenticator(msaAuthenticator);
final IOneDriveClient oneDriveClient = new OneDriveClient
.Builder()
.fromConfig(oneDriveConfig)
.loginAndBuildClient(getActivity());
That will take care of the user authentication flow and then give you a service object that makes interacting with OneDrive straight-forward. See the full example application.
I am working on an IM application with open fire server. I'm implementing Sync Adapter for managing the contacts.
From my sync adapter's onPerformSync() if I access the connection object which I kept in my Application class it returns null.
What am I doing wrong? How should I do that?
My Application class
public class MyApp extends Application {
private Connection connection;
private static MyApp instance;
public static MyApp getInstance() {
return instance;
}
public static void setInstance(MyApp instance) {
MyApp.instance = instance;
}
#Override
public void onCreate() {
super.onCreate();
instance = new MyApp();
}
public Connection getAuthenticatedConnection() throws NullPointerException {
if (instance.connection != null
&& instance.connection.isAuthenticated()) {
return instance.connection;
} else {
// Calling service which will create connection and update the object
Intent intent = new Intent(IM_Service_IntentMessaging.ACTION);
intent = new Intent(getContext(), IM_Service_IntentMessaging.class);
Bundle b = new Bundle();
b.putInt("key", IM_Service_IntentMessaging.KEY_CONNECTION);
intent.putExtras(b);
this.context.startService(intent);
throw new NullPointerException();
}
//service calls this method to update the object
public void setConnection(Connection connection) {
instance.connection = connection;
}
}
And my onPerformSync method is as follows...
#Override
public void onPerformSync(Account account, Bundle bundle, String authority,
ContentProviderClient provider, SyncResult syncResult) {
try {
this.connection = MyApp.getInstance()
.getAuthenticatedConnection();
this.roster = this.connection.getRoster();
} catch (NullPointerException e) {
//this line executes always
Log.e(TAG, "connection null...ending....");
return;
}
this.mAccount = account;
Log.d(TAG, "...onPerformSync...");
getAllContacts();
}
When I tried with creating break point in Application's getAuthenticatedConnection() method it didn't get triggered but the service IM_Service_IntentMessaging which i called from there to create connection is working.
I found the solution to my problem is i should remove the android:process=":sync" from the sync adapter's manifest file declaration. Because of that it treats like different process.
Here is my Contacts activity (my main activity):
public class Contacts extends Delegate {
private static final String TAG = "Contacts";
public View listContactsView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences prefs = getPreferences(MODE_PRIVATE);
String restoredText = prefs.getString("token", null);
if(restoredText == null)
{
Intent intent = new Intent(this, SignIn.class);
startActivity(intent);
}
setContentView(R.layout.activity_contacts);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new ContactListFragment()).commit();
}
//new InEventAPI(this).execute("");
}
...
...
Here is my SignIn activity:
public SignIn extends Delegate {
...
...
public void personSignInDelegate(HttpResponse response, JSONObject result)
{
if(response != null && result != null)
{
switch(response.getStatusLine().getStatusCode())
{
case 200:
try {
SharedPreferences.Editor editor = getPreferences(MODE_PRIVATE).edit();
editor.putString("token", result.get("tokenID").toString());
editor.commit();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
case 401:
Toast.makeText(this, R.string.toastEmailPasswordIncorrect, Toast.LENGTH_LONG).show();
break;
}
}
else
{
Log.d(TAG, "Something went wrong!");
}
}
When I sign in, it commits on SharedPreferences, but when I close my app and re-open, the String becomes null and my OnCreate intents to SignIn again.
Is something that I'm missing?
Just to avoid doubts, my Delegate class:
public class Delegate extends ActionBarActivity {
protected InEventAPI api;
public Delegate() {}
public void personSignInDelegate(HttpResponse response, JSONObject result) {};
}
The problem is most likely with your use of getPreferences(). From the documentation:
Retrieve a SharedPreferences object for accessing preferences that are
private to this activity. This simply calls the underlying
getSharedPreferences(String, int) method by passing in this activity's
class name as the preferences name.
Although both classes extend the Delegate class, they are both unique classes with unique names. This means that getPreferences() in Contacts returns a different SharedPreferenceObject compared to SignIn.
Either use getSharedPreferences(String, int)
Eg.
instead of
getPreferences(MODE_PRIVATE);
change it to
getSharedPreferences ("OneSharedPreference", MODE_PRIVATE);
or override getPreferences() in Delegate so it calls getSharedPreferences() with a unique name.
Alternatively, if you're not using the default SharedPreferences for anything (this is usually used by any PreferenceActivity classes), you can always call
PreferenceManager.getDefaultSharedPreferences() and pass in a Context instance.
Currently I'm writing an adapter class to provide a convenient way for communication with the facebook API.
The way I thought about using it is to run the authentication when the app is starting up, downloading user's private picture, and later in the app publishing updates on users facebook wall using an AsyncFacebookRunner.
However flipping through the documentation it seems for every authorize() implementation the first parameter have to be an activity.
void authorize(Activity activity, final DialogListener listener):
And here I begin to wonder.
Thinking about activities and life cycles what will happen when the activity I threw in will be destroyed? Wouldn't the reference for this object Facebook.mAuthActivity become invalid as well.
I see the logout() method "only" asks for a context.
String logout(Context context) throws ...:
context - The Android context in which the logout should be called: it should be the same context in which the login occurred in order to clear any stored cookies
From what I see I can not guarantee the "login-activity" will still be present as app's uptime increases - actually the opposite is more likely.
Are there any special situations I should consider to prevent the app form total crashing in a later state?
You can try use my FBHelper class.
public class FBHelper {
private SharedPreferences mPrefs;
private Context context;
private final String ACCES_TOKEN = "access_token";
private final String ACCES_EXPIRES = "access_expires";
private Facebook facebook;
private FBHelperCallbacks callback;
public FBHelper(Context context, Facebook facebook)
{
this.context = context;
this.facebook = facebook;
}
public void setSignInFinishListener(FBHelperCallbacks callback)
{
this.callback = callback;
}
public void FacebookSingleSignIn() {
mPrefs = ((Activity)context).getPreferences(Context.MODE_PRIVATE);
String access_token = mPrefs.getString(ACCES_TOKEN, null);
long expires = mPrefs.getLong(ACCES_EXPIRES, 0);
if(access_token != null) {
facebook.setAccessToken(access_token);
}
if(expires != 0) {
facebook.setAccessExpires(expires);
}
/*
* Only call authorize if the access_token has expired.
*/
if(!facebook.isSessionValid()) {
Log.i("Facebook","Facebook session is not valid based on acces token... authorizing again");
facebook.authorize((Activity)context, new String[] {"user_about_me"},new DialogListener() {
#Override
public void onFacebookError(FacebookError e) {
e.printStackTrace();
callback.onError(e.toString());
}
#Override
public void onError(DialogError e) {
Log.i("Facebook","onError inner");
callback.onError(e.toString());
}
#Override
public void onComplete(Bundle values) {
SharedPreferences.Editor editor = mPrefs.edit();
editor.putString(ACCES_TOKEN, facebook.getAccessToken());
Log.i("Facebook","Saving acces token:"+facebook.getAccessToken());
editor.putLong(ACCES_EXPIRES, facebook.getAccessExpires());
editor.commit();
callback.onSignedInFinished(facebook.getAccessToken());
}
#Override
public void onCancel() {
callback.onError("onCancel");
}
});
}
else
{
Log.i("Facebook","Accces token read form preferencesno no need to authorize");
callback.onSignedInFinished(facebook.getAccessToken());
}
}
public String LogOut()
{
try {
//set ACCES_TOKEN to null
mPrefs = ((Activity)context).getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = mPrefs.edit();
editor.putString(ACCES_TOKEN, null);
editor.putLong(ACCES_EXPIRES, 0);
editor.commit();
return facebook.logout(context);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "Error";
}
public static abstract class FBHelperCallbacks{
public abstract void onSignedInFinished(String accesToken);
public abstract void onError(String message);
}
}
This is how you use this class.
public class LogInActivity extends Activity {
private static final String TAG = "LogInActivity";
public static final int REQUEST_CODE = 1;
private Context context;
private Facebook facebook;
private FBHelper fbhelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_log_in);
this.context = this;
Handler pauser = new Handler();
pauser.postDelayed (new Runnable() {
public void run() {
facebook = new Facebook(context.getString(R.string.FACEBOOK_APP_ID));
fbhelper = new FBHelper(context, facebook);
if (aHelper.isLogedIn())
{
//log out
fbhelper.LogOut();
}
else
{
//facebook login
fbhelper.setSignInFinishListener(fbcallback);
fbhelper.FacebookSingleSignIn();
}
}
}, 100);
}
FBHelperCallbacks fbcallback = new FBHelperCallbacks() {
#Override
public void onSignedInFinished(String accesToken) {
Log.d(TAG,"log in finish");
}
#Override
public void onError(String message) {
setResult(RESULT_CANCELED);
finish();
}
};
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
facebook.authorizeCallback(requestCode, resultCode, data);
}
}
aHelper is object that hold some application specific data. Basically you should decide here if you want to log in or log out.
using facebook API for the android is easy and in your case you don't need to save the Facebook instance the only thing you need is to save the authKey of the facebook on the first login then you can use it anywhere.
this means that you can create more than one instance of the facebook object in mutiple activities based on the authKey.
Otherwise you need to put this facebook object in a singleton handler to save it among the application :
class x {
private Facebook obj;
private static x instance;
private x (){
}
public static x getX(){
if(instance == null){
instance = new x();
}
return instance;
}
public void setIt(Facebook obj){
this.obj = obj;
}
public Facebook getIt(){
return obj;
}
}
but this way is not the best way to implement the code you need to create a Facebook instance for each activity using the authKy.