I'm building an app and decided to use Firebase's cloud messaging. The problem I have is that I have no idea how to handle the notification data I get. As far as I know, when you open the notification from background you get intent with the data sent from server in launcher activity. So I have something like this in my launcher activity:
Intent intentOpen;
String message = getIntent().getStringExtra("message");
if(message != null) {
intentOpen = new Intent(this, MainActivity.class);
if (message.equals(this.getResources().getString(R.string.new_shared_file))) {
intentOpen.putExtra(MainActivity.nType, "SHARING");
} else {
intentOpen.putExtra(MainActivity.nType, "SERVICES");
}
}
}
And in my main activity I want to get the nType to determine which fragment should app open:
public static String nType = "notification_type";
if (getIntent().hasExtra(nType)) {
String notification = getIntent().getStringExtra(nType);
if (notification.equals("SHARING")) {
displayView(R.id.nav_shared_files);
} else {
displayView(R.id.nav_account);
}
} else if (savedInstanceState == null) {
displayView(R.id.nav_online_files);
}
So how do I get the String across to the main activity (which I get to after two other activities). Or is there some other way (like from server side) to get the String to the main activity?
Thanks for your answers in advance.
Sure for example you can create enum class with instance:
public enum DataManager {
INSTANCE;
private String data;
public void setData(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
So in first activity when you get data you call:
DataManager.INSTANCE.setData("whateverdata");
And in activity you need data you just call:
String savedData = DataManager.INSTANCE.getData();
In your launcher activity you will get message like this if you send message from console as additional filed option in firebase console:
Regarding image up, you get image as custom data field, otherwise you will not get message in you case. In case if you app is in foreground, you will get your notification data in method onReceiveMessage inside FirebaseNotificationService.
Intent intentOpen;
String image = getIntent().getStringExtra("image"); // or imageUrl
Sending data to other activities must be done over intent arguments, if you want to go this way, you should always put whatever arguments you need to intent when starting next activity. And of course you can choose some other ways to passing data to other activities.
Related
So we're working on this Android app. We've got a login activity that receives some information when the user logs in successfully. We've got a class called SessionManager that handles saving said data to SharedPreferences.
SessionManager always retrieves SharedPreferences from the same file always. It's hardcoded in there.
public SessionManager(Context context) {
this.preferences = context.getSharedPreferences(PREFERENCE_NAME, 0);
this.editor = this.preferences.edit();
this.jsonParser = new Gson();
}
The jsonParser is there so we can save the info as a json object.
public final void storeProfile(UserProfile profile) {
this.editor.putString(STORAGE_KEY, this.jsonParser.toJson(profile));
this.editor.commit();
}
private static final String STORAGE_KEY = "PROFILE";
private String getStoredValue() {
return this.preferences.getString(STORAGE_KEY, null);
}
public UserProfile getStoredProfile() {
String val = getStoredValue();
return (val == null) ? null : this.jsonParser.fromJson(val, UserProfile.class);
}
In theory, this should mean we should be able to store the profile in one activity, then get it back in another activity, right?
Except that's not happening! It looks like I can only retrieve saved information in the same activity where it was saved!
I call storeProfile() in the login activity, then getStoredProfile() in another activity, and it returns null.
I call storeProfile() in the login activity, then getStoredProfile() in the login activity, and it returns the stored profile. It also works if two different SessionManager instances call storeProfile() and getStoredProfile().
I set the stored profile manually in the other activity, and it retrieves the manually stored profile just fine.
Is there some scope rule or something to SharedPreferences that I'm missing?
Turns out I'm a fool and I was accidentally wiping my preferences every time I tried to get them.
What is the best practice for transfer of some (not huge, but often) amount of data from several services to activity?
I'm using BroadcastReceiver in my MainActivity this way:
private class Receiver extends BroadcastReceiver{
public Receiver() {
super();
}
#Override
public void onReceive(Context context, Intent intent) {
// Receiving and showing received symbols
if (intent.getAction() == getString(R.string.key_recognized)) {
char key = intent.getCharExtra("key" , '0');
MainActivity.this.addText(key);
}
// Received transfer ending flag
if (intent.getAction() == getString(R.string.transfer_ended)) {
mServiceWorking = false;
MainActivity.this.mToneButton.setText(getText(R.string.send_text));
}
// Recieving an array (~2Kb) and drawing it on correspondig ImageView
if (intent.getAction() == getString(R.string.spectrum_ready)) {
Spectrum spectrum = (Spectrum) intent.getSerializableExtra("spectrum");
if (spectrum != null) {
drawSpectrum(spectrum);
}
}
}
}
Broadcasts are sended from services somehow like this:
Intent taskIntent = new Intent();
taskIntent.setAction(getString(R.string.key_recognized));
taskIntent.putExtra("key", key);
this.sendBroadcast(taskIntent);
Is this normal code or my hands should be cut off in some way?)
I don't see why passing the data via extras is not the best choice. For me, is the safest, and fastest way to pass data between activities or intents.
You can simple use post method from Handler class
For normal data up to a maximum of 1MB (see this answer) you can use extra data of intents.
For bigger data, or data that is not serializable, you could store that data in one of following:
In a file. Pass URI in extra data.
In the shared preferences.
In a special class that holds your data. Store it in a hashmap, where you pass the key in your extra data of the intent.
The class could look like this:
private static final Map<Long, Object> storage = new HashMap<>();
public static synchronized long store(Object tempData){
if(storage.size() < Long.MAX_VALUE) {
storage.put(storage.size() + 1l, tempData);
return storage.size();
}else{
return -1;
}
}
The store()-Method returns a long value, that can be used to identify the stored data. This way is faster than storing your data in a file and can be bigger than 1MB. But keep in mind, that you have limited memory in android, so don't store to many or too big data. Remove them as soon as you retrieved them.
Thank you every one for giving time to this question.
I have one editview that I need to append when I call appendToMessageHistory method from another Activity.
second Activity
EditText et;
Messaging msg = new Messaging(getApplicationContext(), et);
msg.appendToMessageHistory(username, messegestr);
on Messaging Activity.
private EditText messageHistoryText;
messageHistoryText = (EditText) findViewById(R.id.messageHistory);
private Context mCon;
public Messaging(Context applicationContext, EditText name) {
// TODO Auto-generated constructor stub
this.mCon = applicationContext;
this.messageHistoryText = name;
}
public Messaging() {
}//by default constructor
public void appendToMessageHistorysend(String username, String message) {
if (username != null && message != null) {
if (messageHistoryText != null) {
messageHistoryText.append(Html
.fromHtml(username1));
messageHistoryText.append(Html
.fromHtml(message1));
}
else {
Toast.makeText(mCon,
"hey yo not working value are null",
Toast.LENGTH_LONG).show();
}
}
}
}
}
I still get null for messageHistoryText.
and app crash
again thank you for your time
In your first block of code:
EditText et;
Messaging msg = new Messaging(getApplicationContext(), et);
msg.appendToMessageHistory(username, messegestr);
the object "et" is not initialized... it will crash if you try to access it.
Activity life-cycle is managed by the Android framework. Writing a custom constructor in an Activity is a big mistake (overriding an existing constructor is some rare cases useful).
When you develop for Android you should never call an Activity constructor in your own code. And when I say never I mean NEVER !!!
To instantiate an Activity : you MUST use an Intent and let Android do the job.
Before coding anything else, please be sure to understand the Android activity life-cycle
To answer precisely to your question:
messageHistoryText is null when you use it in your custom constructor because your activity Messaging don't contains any view when you initialize it.
The method findViewById will always return null (whatever the id is) if you call it before setting the content of your Activity.
The most usual way to set the content of an Activity is to call setContentView in the Activity.onCreate() method (but do it after the call to super.onCreate(bundle) otherwise your Activity will not be ready to set it's content)
I have been working on a e-commerce application for a while, and now I have a ListView that displays a list of products - Each products = 1 ImageView and some TextViews-.
I set an onItemClick listener on that ListView, the event that I want to occur when I click on one of that listView products is, Start the i new Activity, 'ProductDetails' that displays more informations on the product I have clicked on.
I have already asked this question somewhere else, but I didn't get a clear answer,
They said that I have to create a new 'list\details' project, but this can't happen now, as I've working on that project for like 20 days, and can't start all over again.
You can't send a TextView or an ImageView from Activity to Activity. what you can send instead is it's contents. so for example if you want to pass the information from the TextView you will need to pass the source String that is displayed there (Or extract the String from the TextView) you do that by passing a Bundle from the calling Activity to the invoked one or simply by putting it as an Extra:
intent.putExtra("string name", value);
and in the following Activity you get this data:
Intent intent = getIntent();
bundle = intent.getExtras();
bundle.getString("string name");
Then in the following Activity create a TextView with the passed String.
Same way is handled with the ImageView by passing the it's path.
Sorry I have posted an answer but was only a link to another site so it was deleted :-(
Here I come again with what could be the solution:
In your first activity, you need to use intent.putExtra(...) and retrieve the datas in your second activity with intent.getExtra(...).
Example activity 1:
i.putExtra("title01", yourDataCollection.get(position).get(KEY_TITLE01));
Activity 2 :
this.title01 = i.getStringExtra("title01");
There is a full project here : http://www.codeproject.com/Articles/507651/Customized-Android-ListView-with-Image-and-Text
Android uses intent to allow Activitys to interact. If you want to show a details of product, probably you have a Class that describe that product and with the information it holds you fill up your ListView. If you let the class Product implements Serializable you can use Intent.putExtra(String name, Serializable obj), to pass the Product description between Activitys. In ProductDetailsActivity you can use:
getIntent(). getSerializableExtra (String name)
to retrive the serializable you put inside the Intent you created to start ProductDetailsActivity
If you pass your model class around many times using Intent, perhaps it could implement the Parcelable interface for convenience. For example:
class ProductData implements Parcelable {
protected String name;
protected String someOtherData;
public static final Parcelable.Creator<ProductData> CREATOR = new Parcelable.Creator<ProductData>() {
#Override
public ProductData createFromParcel(Parcel source) {
return new ProductData(source);
}
#Override
public ProductData[] newArray(int size) {
return new ProductData[size];
}
};
public ProductData(){
}
public ProductData(Parcel source){
readFromParcel(source);
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(someOtherData);
}
public void readFromParcel(Parcel source){
name = source.readString();
someOtherData = source.readString();
}
// Here goes the rest of your model code
}
Then you can easily pass the object to another Activity using an Intent:
intent.putExtra("product", productData);
and later get the data using
ProductData productData = intent.getParcelableExtra("product");
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.