Global variables not passing properly - android

I have created a class that holds common variables and functions and is inherited by the activity classes that interface with the different UI pages in my app. I have been passing information between classes and activities using getVariable() and setVariable(input) functions. Suddenly, I can no longer pass information this way (it had been working well until recent edits, and now I can't figure out which change screwed this up). I have used Log outputs to determine that the data is storing properly - with the setVariable(input) functions - but when called later with the getVariable() functions it returns null. Any thoughts?
*Note, I recently started incorporating fragments into my project, extending FragmentActivity instead of Activity on my main class. I don't think this is causing the problem, but could it? If it does, whats the best practice to pass global variable info, and use fragments?
Code samples:
Main Inherited class:
public class MenuBarActivity extends FragmentActivity {
private String keyA;
private String keyB;
private int token;
private String Salt;
private long expires;
public String getKeyB() {
return keyB;
}
public String getKeyA() {
return keyA;
}
public int getTokenID() {
return token;
}
public void setToken(int tkn) {
token = tkn;
}
public void setKeyB(String kyB) {
keyB = kyB;
}
public void setKeyA(String kyA) {
keyA = kyA;
}
//Other common functions
}
LogIn Activity Class (gets log in info from web, stores into global variables):
public class WebContentGet extends MenuBarActivity{
public int tryLogOn(String uEmail, String pw) {
//call to get new keys on start up
JSONObject jObSend = new JSONObject();
try {
jObSend.put("email", uEmail);
jObSend.put("password", pw);
t.start();
t.join();
if(getStatus() == USER_STATUS_SUCCESSFULLOGIN){
String data = getData();
JSONObject jObReturn = new JSONObject(data);
String kyA = jObReturn.getString("keyA");
String kyB = jObReturn.getString("keyB");
int tkn = Integer.parseInt(jObReturn.getString("tokenID"));
String salt = jObReturn.getString("salt");
long exp = Long.parseLong(jObReturn.getString("expiration"));
int uID = Integer.parseInt(jObReturn.getString("userID"));
// Log outputs confirm data being read properly, and reported to setX() functions
setToken(tkn);
setKeyA(kyA);
setKeyB(kyB);
setSalt(salt);
setExpires(exp);
Log.d("WebContentGet tryLogIn","login values stored");
}
return getStatus();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return getStatus();
}
}
Activity Class, checks if keyA/B/etc stored:
public class UserLogIn2 extends MenuBarActivity implements EmailListener {
String emailIn;
String pwIn;
Context context = this;
#Override
public void onEmailLogInClick(String email, String pw) {
Log.d("UserLogin2", "onEmailLogInClick");
emailIn = email;
pwIn = pw;
emailIn = emailIn.trim();
emailIn = emailIn.toUpperCase();
Log.d("prepped email", emailIn);
pwIn = pwIn.trim();
WebContentGet webOb = new WebContentGet();
int webLog = webOb.tryLogOn(emailIn, pwIn);
if (webLog == USER_STATUS_SUCCESSFULLOGIN) {
int tkn = getTokenID();
long exp = getExpires();
String kya = getKeyA();
String kyb = getKeyB();
String slt = getSalt();
Log.d("UserLogIn2 - token", String.valueOf(tkn));
//Log statements confirm that getX() functions returning null
session.storeLoginSession(emailIn, pwIn, thisUser, tkn, exp, kya, kyb, slt);
Intent intent1 = new Intent(context, MainActivitiy.class);
startActivity(intent1);
} else {
showDialog(this, "Log in failure", "Incorrect Password");
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.userlogin2);
}
}

This cannot work, because you have two differend instances of your MenuBarActivity. Also that is not the way to pass data from one activity to another in android.
If you want to use data from one activity in another activity, you have to add them to an intent in the activity which provides the data, and extract them in the other. For more information see here: How do I pass data between Activities in Android application?
If you don't want to start the activity and send the data with the intent, you have to store the data somewhere e.g. SharedPreferences and fetch them again: How to use SharedPreferences in Android to store, fetch and edit values

Related

Room database - Access data from second activity

I apologize if this is a duplicate.
I'm currently working on a ToDo app and I'm using the Room database library to display a list of items using a RecyclerView. From the main activity, I am able to access the information from the database using the adapter. No problem here.
The thing is that when I press on one of the items, I want to open a DetailActivity where the user can modify the ToDo. Currently I'm getting the desired information using putExtra and hasExtra:
MainActivity
private void initializeAdapterForRecyclerView(){
mAdapter = new ToDoAdapter(new ToDoAdapter.ToDoClickListener() {
#Override
public void onToDoClick(int clickedItemIndex) {
Intent intent = new Intent(ToDoActivity.this, EditorActivity.class);
Log.d(LOG_TAG, "This is " + mAdapter.getToDoPosition(clickedItemIndex).getTitle());
intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(clickedItemIndex));
intent.putExtra("existingTitle", mAdapter.getToDoPosition(clickedItemIndex).getTitle());
intent.putExtra("modifiedTitle", mAdapter.getToDoPosition(clickedItemIndex).getDisplayTitle());
intent.putExtra("existingDescription", mAdapter.getToDoPosition(clickedItemIndex).getDescription());
startActivity(intent);
}
});
mToDoList.setAdapter(mAdapter);
}
DetailActivity
private void getExistingToDoContent() {
Intent intent = getIntent();
if (intent.hasExtra("existingTitle")) {
String displayTitle = intent.getStringExtra("modifiedTitle");
Log.d(LOG_TAG, "Getting the title: " + displayTitle);
mTitleEditText.setText(displayTitle);
String existingDescription = intent.getStringExtra("existingDescription");
Log.d(LOG_TAG, "Getting the description: " + existingDescription);
mDescriptionEditText.setText(existingDescription);
}
}
The issue comes when I want to delete the item from the DetailActivity as I want to do this with the the ViewModel class.
ToDoViewModel
public void delete(ToDo toDo){
mRepository.delete(toDo);
}
ToDoRepository
public void delete(ToDo todo){
new deleteAsyncTask(mToDoDao).execute(todo);
}
private static class deleteAsyncTask extends AsyncTask<ToDo, Void, Void> {
private ToDoDao mAsyncToDoDao;
deleteAsyncTask(ToDoDao dao){
mAsyncToDoDao = dao;
}
#Override
protected Void doInBackground(ToDo... toDos) {
mAsyncToDoDao.deleteToDo(toDos[0]);
return null;
}
}
}
So my question is, is it possible to access the database entry of the clicked item from the DetailActivity to populate the fields and delete / update the entry using the methods from the ViewModel activity? (other than using putExtra / hasExtra)
Thank you

Sending arraylist of objects using serializable

I am storing my JSON data into a arraylist of objects, my object class is Venue and I am passing that data into another activity using serializable from fragment but I am not receiving any values in the actvity. It is receiving the extra. bundle is not null.
My code is:
Fragment:
Intent intent = new Intent(getActivity(), SearchVenueActivity.class);
//pass values
Bundle bundle = new Bundle();
bundle.putSerializable("arrayListVenue",arrayListVenue);
intent.putExtras(bundle);
startActivity(intent);
Activity:
if (getIntent().hasExtra("arrayListVenue")) {
Bundle bundle = getIntent().getExtras();
if(bundle!=null)
rowItems = (ArrayList<Venue>) bundle.getSerializable("arrayListVenue");
else
Log.e("null","null");
}
Venue Class:
public class Venue implements Serializable{
String venue_id;
String venue_name;
String venue_address;
String venue_city;
public String getVenue_city() {
return venue_city;
}
public void setVenue_city(String venue_city) {
this.venue_city = venue_city;
}
public Venue(String venue_id, String venue_name, String venue_address, String venue_city, String venue_zip, String venue_phone, String venue_mobile) {
this.venue_id = venue_id;
this.venue_name = venue_name;
this.venue_address = venue_address;
this.venue_city = venue_city;
this.venue_zip = venue_zip;
this.venue_phone = venue_phone;
this.venue_mobile = venue_mobile;
}
public String getVenue_id() {
return venue_id;
}
public void setVenue_id(String venue_id) {
this.venue_id = venue_id;
}
public String getVenue_name() {
return venue_name;
}
public void setVenue_name(String venue_name) {
this.venue_name = venue_name;
}
}
try this way may this help you
First you want to make
Class Venue implements Serializable
public class Venue implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
}
In your FirstActivity do this way
ArrayList<Venue> VenueArrayList = new ArrayList<Venue>();
Intent intent = new Intent(this,secondActivity.class);
intent.putExtra("VenueArrayList", VenueArrayList);
In your SecondActivity do this way
ArrayList<Venue> VenueArrayList;
VenueArrayList = (ArrayList<Venue>) getIntent().getSerializableExtra(
"VenueArrayList");
Learn from my mistakes.
I've tried to send Arrays and big objects using Serializablethen my app got really slow. Did some tests and found out that when it took a lot of cpu and time to parse it. Therefore (altough its not what you would call best practice) i've created a CacheManager that store object for short time periods and used it to pass larger object between fragments activities.
If you want to make a more readical change to your design (later on in my project i did exactly that) then seperate the data from your fragment/activity completely and load it by LoaderManagers and pass only specific id's or stuff like that (stored stuff in the db in my case so it was easier).
Use intent.putParcelableArrayListExtra("arrayListVenue",arrayListVenue); for putting arraylist to intent and use intent.getParcelableArrayExtra("arrayListVenue") for get arraylist back from intent in your SearchVenueActivity activity
Edit
Tutorial for using parcelable
Use Array List instead of Bundle Elements.
In your Main Activity, try this:
Intent intent = new Intent(this, newactivity.class);
intent.putParceableListArrayExtra("your_array_list_name", arrayList);
In the next activity where you want to retreive it, just use:
getIntent().getExtra("your_array_list_name", new ArrayList<> arrayList2);

return an object Android

I want to return an object with some things in them.
Here is the declaration;
Object user_det = get_user_det();
Here is the function code:
private Object get_user_det() {
Firebase f_user = new Firebase("https://myapp.firebaseio.com/User/");
f_user.addListenerForSingleValueEvent(new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot snap_user) {
// TODO Auto-generated method stub
Iterable<DataSnapshot> rs = snap_user.getChildren();
Iterator<DataSnapshot> irs = rs.iterator();
long allNum2 = snap_user.getChildrenCount();
int maxNum2 = (int)allNum2;
int count_user = 1;
while(irs.hasNext())
{
if(count_user <= maxNum2)
{
Firebase user_data = new Firebase("https://myapp.firebaseio.com/");
AuthData authData = user_data.getAuth();
Map<String, Object> nPost = (Map<String, Object>) irs.next().getValue();
String db_email = nPost.get("email_addr").toString();
if (authData != null) {
String usr_email = authData.getProviderData().get("email").toString();
if(usr_email.equals(db_email))
{
//NB: I WANT TO ADD THE FOLLOWING INTO THE OBJECT
String disp_name = nPost.get("disp_name").toString();
String real_name = nPost.get("real_name").toString();
}
} else {
System.out.println("Failed");
}
}
count_user++;
}
}
#Override
public void onCancelled(FirebaseError arg0) {
// TODO Auto-generated method stub
}
});
return null; //NB: I NEED TO RETURN THE OBJECT HERE.
}
I want to return the string disp_name and real_name but they are inside the addListenerForSingleValueEvent, so how do I get them out and return it to the function.
I have wrote "NB" in the code where I need help with.
Thanks for your time.
If you want to return an object from your method in java, do it like this:
The Object class:
This contains the structure of your Object, and defines what data will be in it. Also includes methods to easily get the data.
private class myObject {
private String name;
private String realName;
//The constructor, so you can set the data when creating the Object.
public myObject (String disp_name, String real_name) {
name = disp_name;
realName = real_name;
}
//Getter methods, to get the data.
public String getRealName() {return realName;}
public String getDisplayName() {return name;}
}
Your code:
private Object get_user_det() {
myObject o; //Declare it, so it can be returned.
...
String disp_name = nPost.get("disp_name").toString();
String real_name = nPost.get("real_name").toString();
o = new myObject(disp_name, real_name); //create it and set the data.
...
return myobject; //return the new Object with the data.
}
To get the data from the Object:
myObject o = get_user_det(); //Call the metod which return our Object.
String realName = o.getRealName(); //Get the data from the Object.
String displayName = o.getDisplayName;
In your case, it would be much easier to use a String array.
Hope this helps.
It's probably easiest to see what's going on, if you add some printlns to your code:
private Object get_user_det() {
Firebase f_user = new Firebase("https://myapp.firebaseio.com/User/");
System.out.println("Adding listener");
f_user.addListenerForSingleValueEvent(new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot snap_user) {
System.out.println("Data received");
}
#Override
public void onCancelled(FirebaseError arg0) {
// TODO Auto-generated method stub
}
});
System.out.println("Returning");
return null; //NB: I NEED TO RETURN THE OBJECT HERE.
}
If you execute this code, you will see that it logs:
Adding listener
Returning
Data received
Most likely, this is not what you expected. But hopefully, it makes sense if you read my explanation below.
Asynchronous loading
When you register your listener:
f_user.addListenerForSingleValueEvent(new ValueEventListener(){
You tell Firebase to start listening for events. It goes off and starts retrieving the data from the server.
Since retrieving the data may take some time, it does this retrieval asynchronously so that your thread isn't blocked. Once the data is completely retrieved, Firebase calls the onDataChange method in your listener.
Between the time you start listening and the time onDataChange is called, your code continues executing. So there is no way to return data that is loaded asynchronously, because by the time your function returns, the data isn't loaded yet.
Solutions
Disclaimer: I am not an expert at solving this problem in Java, so there may be problems with my solutions. If^H^HWhen you find any, please report them in the comments.
I know of three possible solutions to the problem:
force the code to wait for the data to be returned
return a Future that at some point will contain the data
pass a callback into get_user_det and call that function once the data is available
You will probably be tempted to selected option 1, since it matches most closely with your mental modal of loading data. While this is not necessarily wrong, keep in mind that there is a good reason that the loading is done asynchronously. It might be worth taking the "learning how to deal with asynchronicity" penalty now.
Instead of writing up examples for all solutions, I'll instead refer to some relevant questions:
Retrieving data from firebase returning NULL (an answer that uses approach 3)
Is waiting for return, ok?
Java wait() & notify() vs Android wait() & notify() (a question from a user taking approach 1)
How it works:
Firebase uses reflection to build a JSON tree object to save to the database. When you retrieve this JSON tree, you can cast it back to your original object. Just like serializing and deserializing. This means you do not need to handle the keys and values when trying to "rebuild" your object like you are. It can all be done like so:
YourObject object = (YourObject) dataSnapshot.getValue(YourObject.class);
Notice the YourObject.class in the getValue(). This tells firebase to reflect through this class and find the appropriate accessors with the dataSnapshot.
How to do it
Be sure that your object has:
Accessors Appropriate getters and setters for ALL fields - (or annotated with #JsonIgnore if you wish to not save a particular field)
Empty constructor. Your object must provide a constructor that does not modify itself at all.
What your object should look like:
public class YourObject {
private String displayName;
private String realName;
public YourObject() { /*Empty constructor needed for Firebase */ }
// Accessors
public void setRealName(String realName){
this.realName = realName;
}
public String getRealName(){
return this.realName;
}
public String getDisplayName(){
return this.displayName;
}
public void setDisplayName(String displayName){
this.displayName = displayName;
}
}
Then, in any of the firebase callbacks, you can just cast your DataSnapshot in to your object:
public void onDataChange(DataSnapshot snap_user) {
YourObject object = new Object;
if(snap_user.getValue() != null) {
try {
object = (YourObject) snap_user.getValue(YourObject.class); <-- Improtant!!!
} catch(ClassCastException ex) {
ex.printStackTrace();
}
}
return object;
}
Also
It seems you are retrieving many objects. When doing this, I find it best to use the onChildEventListener then for each of the YourObjects in that node, onChildAdded(DataSnapshot ds, String previousChild); will be called.

Copy/share configurations between paid/free versions of Android app?

My Android app comes both as a free and paid version. I have created a library project and two additional Application projects, one 'Free' and one 'Paid' version (signed with the same key, of course). Note that these Application projects are pretty much empty, no settings etc. Hence, the library contains 99% of the code.
My app creates both an SQLite database and a SharedPreferences file with user data. Is it possible to copy these files between the free and paid versions? (The preferences are more important than the database.)
E.g.
User runs the free version. A database and configuration file are created.
User installs the paid version and runs it.
The paid version checks for any free version data and copies it. This is what I want!
Implement a ContentProvider to expose the stored data in your free version.
Ensure the provider is exported (android:exported="true")
Declare a permission in your client application. The protection level should be "signature".
Require the permission declared in (3) as a readPermission for the provider.
In your paid app, add a uses-permission for the permission declared in your free app.
Check for the presence of the provider & load the data into your paid app.
This, of course, only works if you are signing the free and paid apps with the same cert (which most sane people do).
If you don't wish to go to the trouble of implementing a ContentProvider, or if it is possible that both apps may remain installed and used, there is a different solution.
Code and usage
Let us assume that the data in question is in a class:
class DataToBeShared() {
// Data etc in here
}
Then, add a class to both apps as follows:
public class StoredInfoManager {
public static String codeAppType = "apptype";
public static String codeTimestamp = "timestamp";
public static String codeData = "data";
public static String codeResponseActionString = "arstring";
public static String responseActionString = "com.me.my.app.DATA_RESPONSE";
private static int APP_UNKNOWN = 0;
private static int APP_FREE = 1;
private static int APP_PAID = 2;
private static String freeSharedPrefName = "com.me.my.app.free.data";
private static String paidSharedPrefName = "com.me.my.app.paid.data";
// Use only one pair of the next lines depending on which app this is:
private static String prefName = freeSharedPrefName;
private static int appType = APP_FREE;
//private static String prefName = paidSharedPrefName;
//private static int appType = APP_PAID;
private static String codeActionResponseString = "response";
// Provide access points for the apps to store the data
public static void storeDataToPhone(Context context, DataToBeShared data) {
SharedPreferences settings = context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
// Put the data in the shared preferences using standard commends.
// See the android developer page for SharedPreferences.Editor for details.
// Code for that here
// And store it
editor.commit();
}
So far, this is a fairly standard shared preferences storage system. Now is where the fun starts. First, make sure that there is a private method for getting the data stored above, and a private method for broadcasting it.
private static DataToBeshared getData(Context context) {
SharedPreferences settings = context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
DataToBeShared result = new DataToBeShared();
// Your code here to fill out result from Shared preferences.
// See the developer page for SharedPreferences for details.
// And return the result.
return result;
}
private static void broadcastData(Context context, DataToBeShared data, String intentActionName) {
Bundle bundle = new Bundle();
bundle.putInt(codeAppType, appType);
bundle.putParcelable(codeData, data);
Intent intent = new Intext(intentActionString);
intent.putEXtras(bundle);
context.sendBroadcast(intent);
}
Create a BroadcastReceiver class to catch data responses from the other app for our data:
static class CatchData extends BroadcastReceiver {
DataToBeShared data = null;
Long timestamp = 0L;
int versionListeningFor = Version.VERSION_UNKNOWN;
Timeout timeout = null;
// We will need a timeout in case the other app isn't actually there.
class Timeout extends CountDownTimer {
Context _context;
public Timeout(Context context, long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
_context = context;
}
#Override
public void onFinish() {
broadcastAndCloseThisBRdown(_context);
}
#Override
public void onTick(long millisUntilFinished) {}
}
// Constructor for the catching class
// Set the timeout as you see fit, but make sure that
// the tick length is longer than the timeout.
CatchDPupdate(Context context, DataToBeShared dptsKnown, Long timeKnown, int otherVersion) {
data = dptsKnown;
timestamp = timeKnown;
versionListeningFor = otherVersion;
timeout = new Timeout(context, 5000, 1000000);
timeout.start();
}
#Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
if (extras == null) return;
// Check it's the data we want
int sendingVersion = extras.getInt(codeAppType, APP_UNKNOWN);
if (sendingVersion != versionListeningFor) return;
// This receiver has served its purpose, so unregister it.
context.unregisterReceiver(this);
// We've got the data we want, so drop the timeout.
if (timeout != null) {
timeout.cancel();
timeout = null;
}
Long tsInc = extras.getLong(codeTimestamp, 0L);
DataToBeShared dataInc = extras.getParcelable(codeData);
// Now, you need to decide which set of data is better.
// You may wish to use a timestamp system incorporated in DataToBeStored.
if (/* Incoming data best*/) {
data = dpInc;
// Make it ours for the future
storeDataToPhone(context, data);
}
// Send the data out
broadcastAndCloseThisBRdown(context);
}
private void broadcastAndCloseThisBRdown(Context context) {
broadcastData(context, data, responseActionString);
}
}
Now, provide the static access function for the apps to use. Note that it doesn't return anything, that's done by the response catcher above.
public static void geDataFromPhone(Context context) {
DataToBeStored myData = getData(context);
// See security discussion point 2 for this next line
String internalResponseActionString = "com.me.my.app.blah.hohum." + UUID.randomUUID();
// Instantiate a receiver to catch the response from the other app
int otherAppType = (appType == APP_PAID ? APP_FREE : APP_PAID);
CatchData catchData = new CatchData(context, mydata, otherAppType);
context.registerReceiver(catchData, new IntentFilter(internalResponseActionString));
// Send out a request for the data from the other app.
Bundle bundle = new Bundle();
bundle.putInt(codeAppType, otherAppType);
bundle.putString(codeResponseActionString, internalResponseActionString);
bundle.putString(CatchDataRequest.code_password, CatchDataRequest.getPassword());
Intent intent = new Intent(responseActionString);
context.sendBroadcast(intent);
}
That's the core of it. We need one other class, and a tweak to the manifest. The class (to catch the requests from the other app for the data:
public class CatchDataRequest extends BroadcastReceiver {
// See security discussion point 1 below
public static String code_password = "com.newtsoft.android.groupmessenger.dir.p";
public static String getPassword() {
return calcPassword();
}
private static String calcPassword() {
return "password";
}
private static boolean verifyPassword(String p) {
if (p == null) return false;
if (calcPassword().equals(p)) return true;
return false;
}
#Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle == null) return;
String passwordSent = bundle.getString(code_password);
if (!verifyPassword(passwordSent)) return;
int versionRequested = bundle.getInt(StoredInfoManager.codeAppType);
String actionStringToRespondWith = bundle.getString(StoredInfoManager.codeResponseActionString);
// Only respond if we can offer what's asked for
if (versionRequested != StoredInfoManager.appType) return;
// Get the data and respond
DataToBrStored data = StoredInfoManager.getData(context);
StoredInfoManager.broadcastData(context, data, actionStringToRespondWith);
}
}
In the manifest, be sure to declare this class as a Receiver with the action name matching StoredInfoManager.responseActionString
<receiver android:name="com.me.my.app.CatchDataRequest" android:enabled="true">
<intent-filter>
<action android:name="com.me.my.app.DATA_RESPONSE"/>
</intent-filter>
</receiver>
Using this is relative simple. The class you are using the data in must extend BroadcastReceiver:
public class MyActivity extends Activity {
// Lots of your activity code ...
// You'll need a class to receive the data:
MyReceiver receiver= new MyReceiver();
class MyReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
if (extras == null) return;
// Do stuff with the data
}
}
// But be sure to add the receiver lines to the following methods:
#Override
public void onPause() {
super.onPause();
this.unregisterReceiver(receiver);
}
#Override
public void onResume() {
super.onResume();
this.registerReceiver(receiver, new IntentFilter(StoredInfoManager.receiver_action_string));
}
}
// To store the data
StoredInfoManager.storeDataToPhone(contextOfApp, data);
// To retrieve the data is a two step process. Ask for the data:
StoredInfoManager.getData(contextOfApp);
// It will arrive in receiver, above.
}
Security
The weakness of this method is that anyone can register a receiver to catch the communication between the two apps. The code above circumvents this:
Make the request broadcast hard to fake through the use of a password. This answer sin't a place to discuss how you might make that password secure, but it is important to realise that you can't store data when you create the password to check it against later - it's a different app that will be checking.
Make the response harder to catch by using a unique action code each time.
Neither of these is fool proof. If you're simply passing around favourite app colours, you probably don't need any of the security measures. If you're passing around more sensitive information, you need both, and you need to think about making the password appropriately secure.
Other improvement
If you wish to check if the other version is installed before sending out the query and waiting for an answer, see Detect an application is installed or not?.
I've collected information from a number of stackoverflow answers to provide a way to copy all SharedPreference data from one app to another. In my particular case I'm using product flavours for a free and a pro app, and I want to copy from free to pro.
CAUTION: This only works if you have not released either version on the play store. If you add (or remove) sharedUserId to your app after it is on the play store, your users won't be able to update without uninstalling. I learnt this the hard way. Thanks Google..
Add sharedUserId to your manifest in both apps. Note that this will only work if both apps are signed with the same certificate.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="my.package.name.free"
android:sharedUserId="my.package.name">
Then call this method when you first intialize the pro app.
private void getSettingsFromFreeApp() {
// This is a build config constant to check which build flavour this is
if (BuildConfig.IS_PRO) {
try {
Context otherAppContext = this.createPackageContext("my.package.name.free", Context.MODE_PRIVATE);
SharedPreferences otherAppPrefs = PreferenceManager.getDefaultSharedPreferences(otherAppContext);
Map<String, ?> keys = otherAppPrefs.getAll();
SharedPreferences.Editor editor = prefs.edit();
for(Map.Entry<String, ?> entry : keys.entrySet()){
Object value = getWildCardType(entry.getValue());
Log.d("map values", entry.getKey() + ": " + entry.getValue());
if (entry.getValue() instanceof Boolean) {
editor.putBoolean(entry.getKey(), (boolean) value);
editor.apply();
} else if (value instanceof Long) {
editor.putLong(entry.getKey(), (long) value);
editor.apply();
} else if (value instanceof Float) {
editor.putFloat(entry.getKey(), (float) value);
editor.apply();
} else if (value instanceof Integer) {
editor.putInt(entry.getKey(), (int) value);
editor.apply();
} else if (value instanceof String) {
editor.putString(entry.getKey(), String.valueOf(value));
editor.apply();
}
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
private Object getWildCardType(Object value) {
return value;
}
Also, according to this answer you will want to call getSettingsFromFreeApp() before any other call to get preferences in your app.

Help with passing ArrayList and parcelable Activity

So I've been googling most of yesterday and last nite and just can't seem to wrap my head around how to pass an arraylist to a subactivity. There are tons of examples and snippets passing primitive data types, but what I have is an arraylist of type address (address.java below).
I've found a lot of stuff on stackoverflow and around the web on this, but nothing that got a lot of attention except for one with a GeoPoint example. Again, it looked to me like they just flattened the GeoPoint object into two integers and passed it in. I can't do that because my address class may expand to include integers, floats, whatever. Right now, the test app below is only two strings for simplicity. I thought if I could get the parcelalbe stuff working with that, the rest could follow.
Can someone post a working example for an ArrayList of a non-primitive object, or perhaps add code below to make this work?
UPDATE: code below is now working after replies/editing. Thanks!
/* helloParcel.java */
public class helloParcel extends Activity
{
// holds objects of type 'address' == name and state
private ArrayList <address> myList;
#Override
public void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
setContentView (R.layout.main);
Button b1 = (Button) findViewById(R.id.button1);
b1.setOnClickListener(ocl);
myList = new ArrayList();
address frank = new address ("frank", "florida");
address mary = new address ("mary", "maryland");
address monty = new address ("monty", "montana");
myList.add (frank);
myList.add (mary);
myList.add (monty);
// add the myList ArrayList() the the extras for the intent
}
OnClickListener ocl = new OnClickListener()
{
#Override
public void onClick(View v)
{
// fill parceable and launch activity
Intent intent = new Intent().setClass(getBaseContext (), subActivity.class);
// for some reason, I remember a posting saying it's best to create a new
// object to pass. I have no idea why..
ArrayList <address> addyExtras = new ArrayList <address>();
for (int i = 0; i < myList.size(); i++)
addyExtras.add (myList.get(i));
intent.putParcelableArrayListExtra ("mylist", addyExtras);
startActivity(intent);
}
};
}
/* address.java */
public class address implements Parcelable
{
private String name;
private String state;
private static String TAG = "** address **";
public address (String n, String s)
{
name = n;
state = s;
Log.d (TAG, "new address");
}
public address (Parcel in)
{
Log.d (TAG, "parcel in");
name = in.readString ();
state = in.readString ();
}
public String getState ()
{
Log.d (TAG, "getState()");
return (state);
}
public String getName ()
{
Log.d (TAG, "getName()");
return (name);
}
public static final Parcelable.Creator<address> CREATOR
= new Parcelable.Creator<address>()
{
public address createFromParcel(Parcel in)
{
Log.d (TAG, "createFromParcel()");
return new address(in);
}
public address[] newArray (int size)
{
Log.d (TAG, "createFromParcel() newArray ");
return new address[size];
}
};
#Override
public int describeContents ()
{
Log.d (TAG, "describe()");
return 0;
}
#Override
public void writeToParcel (Parcel dest, int flags)
{
Log.d (TAG, "writeToParcel");
dest.writeString (name);
dest.writeString (state);
}
}
/* subActivity.java */
public class subActivity extends Activity
{
private final String TAG = "** subActivity **";
private ArrayList <address> myList;
#Override
protected void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
Log.d (TAG, "onCreate() in subActivity");
setContentView(R.layout.subactivity);
TextView tv1 = (TextView) findViewById(R.id.tv_sub);
myList = getIntent().getParcelableArrayListExtra ("mylist");
Log.d (TAG, "got myList");
for (int i = 0; i < myList.size (); i++)
{
address a = myList.get (i);
Log.d (TAG, "state:" + a.getState ());
tv1.setText (a.getName () + " is from " + a.getState ());
}
}
}
I can see a number of problems here:
Why use addressParcelable? Why not make address implement Parcelable, and then use:
intent.putParcelableArrayListExtra( "addresses", addyExtras );
Your parcelable object must include a static CREATOR. See the documentation for details.
You are not actually adding any extras to the intent before you call startActivity(). See point 1 for a suggestion here.
I think that you will need to address all of these issues in order to get it working.
It can be done MUCH simpler, without all the pain-in-the-ass of implementing Parcelable...
ArrayList (but NOT any List) is Serializable. So, you can put the entire list using putExtra() and retrieve it using getSerializableExtra(), as Sam said.
BUT, I want to add one more important thing: the object your array list stores has to also implement Serializable... and all other complex objects that the object may contain (in your case none) must also implement that (so it's recursive - in order to serialize an object, you must be able to serialize all of its fields).
Now, you might be asking yourself why implementing Serializable instead of Parcelable when there are already methods for reading and writing array lists of parcelables?
Well... the difference is simplicity - just add implements Serializable and optionally private static final long serialVersionUID = SOME_CONSTANT and you're DONE! That is the reason why I never use Parcelable - you can do all those things using Serializable with literally 2 lines of code - instead of many method inheritances and all that stuff...
You can pass Serializable objects via putExtra. ArrayList implements Serializable.
Mike dg is correct!
putExtra() and getSerializable() will store and retrieve an ArrayList<> of your custom objects, with no interface implementing required. Worked for me!

Categories

Resources