App update deletes old shared prefs data from internal storage - java

In my app, I have a database of objects that are generated by the user and then saved to the internal storage using shared prefs when the user leaves the app. Now when the user re-opens the app, that data is retrieved and presented to the user for further editing. I noticed that when I roll out an update to my app and the user installs it, all the data is lost. I tried retrieving it by saving the app's current version code using shared prefs and then comparing it to the current one in order to know when it's an app update and then i call the Read & Write data methods to retrieve the old data but with no luck. Any ideas on how i should approach this issue?
SerializeGLB.java:
public class SerializeGLBData {
/**
* Writes the Global User Box's cardList to the user's internal storage using the Gson
* library so that the user doesn't lose his/her data.
* #param cardList The list to write to the internal storage
* #param context Getting the app's current context
*/
public static void Write(ArrayList<Card> cardList, Context context) {
SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = appPrefs.edit();
Gson gson = new Gson();
String cardsGLBJson = gson.toJson(cardList);
editor.putString("cardsGLB",cardsGLBJson);
editor.apply();
editor.commit();
Log.d("WriteData","Data written successfully!");
}
/**
* Reads the cards list that gets saved when the app closes
* #param context Get the app's current context
* #return Returns an ArrayList of Card Objects containing the card info
*/
public static ArrayList<Card> ReadCards(Context context) {
SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(context);
Gson gson = new Gson();
String cardsGLBJson = appPrefs.getString("cardsGLB","");
Type type = new TypeToken<ArrayList<Card>>(){}.getType();
return gson.fromJson(cardsGLBJson,type);
}
}
private void checkForFirstRun() {
final String PREF_VERSION_CODE_KEY = "version_code";
final int DOESNT_EXIST = -1;
// Get current version code
int currentVersionCode = BuildConfig.VERSION_CODE;
// Get saved version code
SharedPreferences prefs = getSharedPreferences(PREFS_NAME,MODE_PRIVATE);
int savedVersionCode = prefs.getInt(PREF_VERSION_CODE_KEY, DOESNT_EXIST);
// Check for first run or upgrade
if(currentVersionCode == savedVersionCode) {
// This is just a normal run
Log.d("RUN_TYPE:" , "Normal Run");
} else if(savedVersionCode == DOESNT_EXIST) { // This is a new install(or the user cleared the shared prefs)
CallWriteDataMethods(this);
Log.d("RUN_TYPE:", "New Install");
// Showing the tutorial page when the app starts for the first time
Intent tutorialIntent = new Intent(this, Tutorial.class);
startActivity(tutorialIntent);
UsernameDialog dialog = new UsernameDialog();
dialog.setCancelable(false);
dialog.show(getFragmentManager(),"USERNAME_DIALOG");
} else if(currentVersionCode > savedVersionCode) { // This is an upgrade
CallWriteDataMethods(this);
Log.d("RUN_TYPE:","Update");
}
// Update the shared prefs with the current version code
prefs.edit().putInt(PREF_VERSION_CODE_KEY,currentVersionCode).apply();
return;
}
public static void CallWriteDataMethods(Context context) {
// Write all the -empty- data from GlobalDataHolder to the internal memory to avoid a first time read error
SerializeGLBData.Write(GlobalDataHolder.cards,context);
// Write all the -empty- data from JPDataHolder to the internal memory to avoid a first time read error
SerializeJPData.Write(JPDataHolder.cards,context);
}
/**
* Calls every available Read method to retrieve all available data from the GLB database
*/
public static void callReadDataMethodsGLB(Context context) {
GlobalDataHolder.cards = SerializeGLBData.ReadCards(context);
Log.i("Read Methods[GLB]", "ReadMethods called!");
}
/**
* Calls every available Read method to retrieve all available data from the JP database
*/
public static void callReadDataMethodsJP(Context context) {
JPDataHolder.cards = SerializeJPData.ReadCards(context);
Log.i("Read Methods[JP]", "ReadMethods called!");
}

How does your SerializeGLBData.Write function works? Because by reading your code, when you are in the case of an upgrade, you are only calling the CallWriteDataMethods directly, and according to your comment in it:
// Write all the -empty- data from GlobalDataHolder to the internal memory to avoid a first time read error
You are writing the memory with empty data. Are your writing functions checking if data exists before putting empty data in it?
So something like
if(!prefs.contains("your_data_key")) {
// your code to add data
}

Related

Sqlite query not showing hindi words from precreated database?

My Database Adapter Class
public class DatabaseAdapter {
// Declare a DatabaseHelper object reference
DatabaseHelper helper;
// Declare a SQLiteDatabase object reference.
SQLiteDatabase db;
// SQLiteDatabase class has methods to create, delete, execute SQL commands and perform other common database
// management tasks.
// Define an ArrayList of Term object.
ArrayList<Term> termsList = new ArrayList<Term>();
// What is Term?
// You'll create a Term class to contain and model the information and make it more easy to implement.
// Define the constructor for DatabaseAdapter
public DatabaseAdapter(Context context){
// Instantiate helper
helper = new DatabaseHelper(context);
// Call getWritableDatabase() method on helper. This is going to give you an object of SQLiteDatabase. Store that in db.
db = helper.getWritableDatabase();
// Now, this SQLiteDatabase object, db, is going to represent the database you have and you are going to use that object
// to perform the different queries that you want to do, for example, insert, update or delete from database.
}
// Define a method to close the database
public void close() {
helper.close();
}
public int deleteData(long id) {
// Define the whereArgs String array
String whereArgs[] = {""+id};
// Call delete() method on db
return db.delete(DatabaseHelper.TABLE_NAME, DatabaseHelper.KEY_ID + "=?", whereArgs);
// delete() returns the number of rows deleted as an integer.
}
public int updateTermFullForm(long id, String meaning) {
// To update the database, you need to create an object of the class called ContentValues that acts like a map, inside which you can
// put your key-value pairs.
// Here, what is expected is the name of the key that you give here is the name of the column in your table
// and the value you want to put inside the column goes in the second parameter.
ContentValues contentValues = new ContentValues();
contentValues.put(DatabaseHelper.KEY_MEANING, meaning);
// You need to create whereArgs[] array. whereArgs[] is just an array that contains the values that are substituted inside the
// question mark (?) of whereClause at run-time, when you are executing the query.
// whereArgs[] is going to contain the values for against you want to compare.
String whereArgs[] = {""+id};
// Call update() method on db
return db.update(DatabaseHelper.TABLE_NAME, contentValues, DatabaseHelper.KEY_ID + "=?", whereArgs);
// update() returns the number of rows affected as an integer.
// The plain sql statement for this can be:
// UPDATE ct SET full_form="New Value" WHERE _id=2
}
public long insertTerm(String words, String meaning) {
// Define a new ContentValues object
ContentValues contentValues = new ContentValues();
// Add term and fullForm into that
contentValues.put(DatabaseHelper.KEY_WORDS, words);
contentValues.put(DatabaseHelper.KEY_MEANING, meaning);
// Call insert() method on db object and return
return db.insert(DatabaseHelper.TABLE_NAME, null, contentValues);
}
// Next, define a method that returns an ArrayList of specific Term objects where the term starts with the String in parameter.
public ArrayList<Term> getSomeTerms(String termStartsWith){
// Call query() method on db and store the returned cursor.
Cursor cursor = db.query(DatabaseHelper.TABLE_NAME, new String[]{DatabaseHelper.KEY_ID, DatabaseHelper.KEY_WORDS,
DatabaseHelper.KEY_MEANING}, DatabaseHelper.KEY_WORDS + " like '"
+ termStartsWith + "%'",null,null,null,null);
// Here, % is a wildcard character which indicates 0 or any number of characters. So, there can be any number of characters
// after “A”, or "B" or "S" etc.
// The plain sql statement for this can be:
// SELECT * FROM ct WHERE terms LIKE 'A%';
// Use a while loop to traverse the database and populate the ArrayList of Term objects
while (cursor.moveToNext()){
// Get the database column index or position by passing the column name
int index1 = cursor.getColumnIndex(DatabaseHelper.KEY_ID);
// Now, get the value of id for that cell
long id = cursor.getInt(index1);
// Do the same thing to get values from other two columns
int index2 = cursor.getColumnIndex(DatabaseHelper.KEY_WORDS);
String words = cursor.getString(index2);
int index3 = cursor.getColumnIndex(DatabaseHelper.KEY_MEANING);
String meaning = cursor.getString(index3);
// Create a Term object from database values
Term term = new Term(id, words, meaning);
// Add the Term object to termsList
termsList.add(term);
}
// return termList
return termsList;
}
// For managing all the operations related to the database, a helper class has been provided by Android
// and it is called SQLiteOpenHelper.
// It takes care of opening the database if it exists, creating it if it does not exists, and upgrading it as necessary.
// So, inside DatabaseAdapter you'll create a static inner class that extends SQLiteOpenHelper.
private static class DatabaseHelper extends SQLiteOpenHelper{
// Define some private static final String variables to store information related to the database
private static final String DATABASE_NAME ="Test.db";
// Database name must be unique within an app, not across all the apps.
private static final String TABLE_NAME = "words";
// When you do change the structure of the database change the version number from 1 to 2
private static final int DATABASE_VERSION = 7;
static final String KEY_ID = "id";
static final String KEY_WORDS = "words";
static final String KEY_MEANING = "meaning";
private Context context;
// Define the constructor
public DatabaseHelper(Context context){
super(context, DATABASE_NAME, null, DATABASE_VERSION);
// Store the context received from constructor into this class's context variable
this.context = context;
}
// Since, you're not creating or upgrading the database since you're using a pre-created database file
// copied to the right location, you don't need to write any code inside onCreate() or onUpgrade().
#Override
public void onCreate(SQLiteDatabase db) {
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}
My PreCreate Database Class
public class PreCreateDB {
static String destPath;
static String destPathwithFilename;
// Lets define copyDB() method
public static void copyDB(Context context){
// Defile two String variables containing path upto "database" folder and "CTDB" file respectively
destPath = "/data/data/" + context.getPackageName() + "/databases";
destPathwithFilename = destPath+"/Test.db";
// Create two File objects from those Strings
File fPath = new File(destPath);
File fPathWithName = new File(destPathwithFilename);
// Now, the question is, why we created two separate File objects?
// It's because in some devices databases folder will be automatically created by Android system.
// In some other devices it won't be there by default.
// So, we need to check if it's not present in the device.
if(!fPath.exists()){
// If true, you'll create the databases folder
fPath.mkdirs();
// And then copy the CTDB Database file from assets folder to databases folder.
// You'll define a method named rawCopy that takes an InputStream and an OutputStream.
// This method will copy the file.
try {
rawCopy(context.getAssets().open("Test.db"), new FileOutputStream(destPath+"/Test.db"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void rawCopy(InputStream inputStream, OutputStream outputStream) throws IOException {
// To copy 1k bytes at a time, create a byte array of size 1024
byte[] buffer = new byte[1024];
// Declare an integer variable to store the total number of bytes read from the buffer.
int length;
// If you call read() method on inputStream object and pass buffer as parameter, it will read 1024 bytes at a time.
// It returns -1 if there is no more data because the end of the stream has been reached.
// Using this information you use a while loop to read from the inputStream and write to the outputStream.
// This copies the database file CTDB from assets folder to data/data/[package-name]/databases folder.
while((length = inputStream.read(buffer)) > 0){
outputStream.write(buffer, 0, length);
}
// Close the input and output streams once you're done
inputStream.close();
outputStream.close();
}
public static void resetDB(Context context) {
// Call rawCopy() inside try block
try {
rawCopy(context.getAssets().open("Test.db"), new FileOutputStream(destPathwithFilename));
} catch (IOException e) {
e.printStackTrace();
}
}
}
MY Show Term Class
public class ShowTerm extends AppCompatActivity {
// Declare a DatabaseAdapter object reference
static DatabaseAdapter databaseAdapter;
// Declare a RecyclerView object reference
static RecyclerView rvTerms;
// Declare an Adapter object reference
TermsAdapter termsAdapter;
// Declare a LayoutManager object reference
RecyclerView.LayoutManager layoutManager;
// Define an ArrayList of type Term
static ArrayList<Term> termsList = new ArrayList<>();
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.show_term);
// Get the received String from Intent
String termStartsWith = getIntent().getStringExtra("termStartsWith");
// Instantiate DatabaseAdapter class and pass this for the Context
databaseAdapter = new DatabaseAdapter(this);
// Call getSomeTerms() on databaseAdapter object and store the returned ArrayList in
// termsList
termsList = databaseAdapter.getSomeTerms(termStartsWith);
// Obtain a handle for the RecyclerView
rvTerms = findViewById(R.id.rvTerms);
// You may use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
rvTerms.setHasFixedSize(true);
// Instantiate the linear layout manager
layoutManager = new LinearLayoutManager(this);
// Set the layout with RecyclerView
rvTerms.setLayoutManager(layoutManager);
// Create an instance of TermsAdapter. Pass context, termsList and the
// RecyclerView to the constructor
termsAdapter = new TermsAdapter(this, termsList, rvTerms);
// Finally, attach the adapter with the RecyclerView
rvTerms.setAdapter(termsAdapter);
}
}
My Bengali + Hindi Mix Sqlite Database
MainActivity Ui
This Sqlite query giving blank results for Hindi words but English words are showning Properly
My MainActivity Class
public class MainActivity extends AppCompatActivity {
// Store the text to be shared in a String
String shareBody = "Download CTD App now and know about all the important Computer Terms and their Full Forms: \n" +
"https://play.google.com/store/apps/details?id=com.sandipbhattacharya.computertermsdictionary";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Create a class containing static methods to copy the database file
// from assets folder into: data/data/package-name/databases folder, from where the app can access it.
// Lets name it PreCreateDB.
// From MainActivity, call the copyDB method of PreCreateDB and pass "this" for Context
PreCreateDB.copyDB(this);
}
public void show(View view) {
// We have set a text with every button. This text simply contains an alphabet.
// Get the clicked Button's text and store in a String variable
String termStartsWith = ((Button) view).getText().toString().trim();
// Create an Intent to go to another Activity where you can show all the Terms that start with a letter termStartsWith contains
Intent intent = new Intent(this, ShowTerm.class);
// Set termStartsWith with the Intent object as Extra
intent.putExtra("termStartsWith", termStartsWith);
// Start the Activity with the Intent
startActivity(intent);
// Create the ShowTerm class.
}
public void reset(View view) {
// You'll use Android AlertDialog to ask the user about his/her choice to continue or discontinue the reset operation.
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Resetting will delete all your personal data. Proceed?");
builder.setCancelable(true);
builder.setPositiveButton("Yes",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// Here, you call a method and pass MainActivity.this as context,
// to replace the database file from assets folder to databases folder.
PreCreateDB.resetDB(MainActivity.this);
}
});
builder.setNegativeButton("No",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alertDialog = builder.create();
alertDialog.show();
}
public void addNew(View view) {
// You'll use an Intent to go to AddNew Activity
Intent intent = new Intent(this, AddNew.class);
startActivity(intent);
}
public void rate(View view) {
// Create an Intent that opens a URL in Google Play
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://play.google.com/store/apps/details?id=" + getPackageName()));
// As you configure this intent, pass "com.android.vending" into Intent.setPackage() so that users see your app's details
// in the Google Play Store app instead of a chooser. Make sure your emulator has pre-installed Play Store app.
intent.setPackage("com.android.vending");
// Start the Activity
try {
startActivity(intent);
}catch (ActivityNotFoundException e) {
Toast.makeText(this, "Couldn't launch Play Store", Toast.LENGTH_LONG).show();
}
}
public void shareApp(View view) {
// Create a send Intent
Intent sendIntent = new Intent(Intent.ACTION_SEND);
// Set the Sharing Type
sendIntent.setType("text/plain");
// Pass your sharing content using the putExtra() method of the Intent
sendIntent.putExtra(Intent.EXTRA_SUBJECT, "Share CTD App");
sendIntent.putExtra(Intent.EXTRA_TEXT, shareBody);
// Next, instruct Android system to let the user choose their sharing medium
startActivity(Intent.createChooser(sendIntent, "Share using"));
// This will pass the sendIntent along with a title to be displayed at the top of the chooser.
// When the user chooses an application from the list, your share content will be passed to that application,
// where he/she will be able to edit the content before sending it if they wish to do so.
}
}
Please Help me because im beginner in android development and it is very important for my dictionary app 🙏
Solved the problem, in Sqlite database your table creation rules are also important when using external database.

How can i make a network request when the user opens an activity but only if 24 hours have passed since the last request?

I have an activity that sends a network request to get some data from an API and saves it in a database, but its sending the request every time the user opens the activity, Is there a way to make the request only if 24 hours have passed since the last request ?
you can store your last hit time in shared preference and when the user opens that activity gets the last saved time and compare with current time if it is greater the 24 hours then hit your API otherwise don't do any action. To create a shared reference class like this:-
public class AppPrefrences {
private static SharedPreferences mPrefs;
private static SharedPreferences.Editor mPrefsEditor;
public static String getLastTime(Context ctx) {
mPrefs = PreferenceManager.getDefaultSharedPreferences(ctx);
return mPrefs.getString("lstTime", "");
}
public static void setLastTime(Context ctx, String value) {
mPrefs = PreferenceManager.getDefaultSharedPreferences(ctx);
mPrefsEditor = mPrefs.edit();
mPrefsEditor.putString("lastTime", value);
mPrefsEditor.commit();
}
public static void clearAllData(Context ctx) {
mPrefs = PreferenceManager.getDefaultSharedPreferences(ctx);
mPrefsEditor = mPrefs.edit();
mPrefsEditor.clear();
mPrefsEditor.commit();
}
}
to set your time do this:-
setLastTime(this, "current time");
to get last time:-
String lastTime = getLastTime(this);
now in lastTime you got your last time api hit time now get current time and compare both time and do your code.

Facebook SDK blocking upload of String to Firebase

I'm developing an Android app which uses Facebook Login. Login's working fine and I'm able to get back info via Facebook Graph API calls.
I'm trying upload a string to my Firebase database and the string (titled parentFirstNameFromFacebook) is the first name of the user who signed into Facebook. I'm trying to eventually upload a parent object with parentFirstNameFromFacebook in its setName() method.
doneCreatingNameAndPasswordFAB.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
gettingTextFromNameAndPasswordEditTexts();
//region creating new Parent object and setting required variables
Parent coOpCreatingParent = new Parent();
coOpCreatingParent.setCoopCreator(true);
coOpCreatingParent.setNumOfHoursOwned(0);
Bundle params = new Bundle();
params.putString("fields", "id,first_name");
new GraphRequest(AccessToken.getCurrentAccessToken(), "me", params, HttpMethod.GET,
new GraphRequest.Callback() {
#Override
public void onCompleted(GraphResponse response) {
if (response != null) {
try {
JSONObject data = response.getJSONObject();
parentFirstNameFromFacebook = data.getString("first_name");
SharedPreferences fBookSharedPref;
SharedPreferences.Editor editor;
fBookSharedPref = getSharedPreferences(Constants.FBOOK_NAME_SHARED_PREF, Context.MODE_PRIVATE);
editor = fBookSharedPref.edit();
editor.putString(Constants.FBOOK_NAME_SHARED_PREF, parentFirstNameFromFacebook);
editor.apply();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).executeAsync();
SharedPreferences fBookSharedPref = getSharedPreferences(Constants.FBOOK_NAME_SHARED_PREF, Context.MODE_PRIVATE);
fbookFirstNameForUpload = fBookSharedPref.getString(Constants.FBOOK_NAME_SHARED_PREF, null);
coOpCreatingParent.setName(fbookFirstNameForUpload);
getProfileImageUrlFromFBookGraph();
coOpCreatingParent.setImageUrl(parentImageIDFromFBookGraph);
ArrayList<Child> newChildArrayList = new ArrayList<>();
coOpCreatingParent.setChildren(newChildArrayList);
//endregion
//region creating new ArrayList<Parent> adding Parent object from above
ArrayList<Parent> coOpParentArrayList = new ArrayList<>();
coOpParentArrayList.add(coOpCreatingParent);
//endregion
getReferenceOfCoOpBeingCreated();
//region uploading entire new Co-Op object to Firebase
CoOp coOpObjectBeingUploaded = new CoOp(coOpKey, enteredNewCoOpPassword, enteredNewCoOpName, coOpParentArrayList);
referenceOfCoOp.setValue(coOpObjectBeingUploaded);
//endregion
//region going to AddChildren Activity with Co-Op key in intent
Intent intentForNewActivity = new Intent(getBaseContext(), AddChildrenActivity.class);
intentForNewActivity.putExtra(Constants.CO_OP_REFERENCE_TO_CHILD_ACTIVITY_KEY, coOpKey);
startActivity(intentForNewActivity);
//endregion
}
});
In the Firebase screenshot below, parentsInCoOp is an ArrayList and the 0 below it is the Parent object. I'm trying to set a name and Facebook URL string as other variables for that Parent object.
Firebase screenshot
Whatever I do, the name doesn't show up on Firebase! I'm not sure what I'm doing wrong!
I also tried using Shared Preferences in case there's an issue with setting parentFirstNameFromFacebook within the Facebook call's onCompleted() method. I put parentFirstNameFromFacebook in SharedPref and then got it out to pass it through parent.setName().
Once I successfully upload the first name, then I'll do the same process for the fbook image URl string.
Anyone have any advice? Thanks!
You are performing the Facebook API GraphRequest using the executeAsync() method. The request is processed on a separate thread and the onComplete() response is received asynchronously. I don't use the Facebook API, but am guessing the request requires communication with the Facebook servers and will require many milliseconds to deliver a response.
The result is that these statements
fbookFirstNameForUpload =
fBookSharedPref.getString(Constants.FBOOK_NAME_SHARED_PREF, null);
coOpCreatingParent.setName(fbookFirstNameForUpload);
execute before the statements in the callback have executed and stored the name in preferences.

Save ArrayList<Object> in Android [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
I am developing an app which will send email in several stages, but I have to check the internet connection. If the user has an internet connection , then I just send the email, but if the user does not have internet then I have to save the email request and send the email when the user regains internet connectivity.
I built an ArrayList and added email requests to that list when the user does not have a connection. How can i save the ArrayList in App permanently? I have to store the list permanently because I need the list even after the user closes and reopens the App.
Simplest answer you need an SQL database of some sort to save the data, although without your code listed you will not get a specific answer.
Learn to use SQL Lite within the application before moving onto connecting to a DB through an internet connection.
This helped me during university: Head over to http://www.edumobile.org/android/android-development/mini-notepad/.
First convert your list to a json String, you can use gson, jackson or any other json converter.
add the following class to your project:
public class JSONCache {
private static Context context = null;
public static void init(Context context) {
if (JSONCache.context == null) {
JSONCache.context = context;
}
}
public static String loadData(String prefName, String key) {
SharedPreferences prefs = context.getApplicationContext().getSharedPreferences(prefName, Context.MODE_PRIVATE);
return prefs.getString(key, null);
}
public static void saveData(String prefName, String key, String json ) {
SharedPreferences prefs = context.getApplicationContext().getSharedPreferences(prefName, Context.MODE_PRIVATE);
SharedPreferences.Editor ed = prefs.edit();
ed.putString(key, json);
ed.commit();
}
public static void deleteAllData(String prefName) {
SharedPreferences prefs = context.getApplicationContext().getSharedPreferences(prefName, Context.MODE_PRIVATE);
SharedPreferences.Editor ed = prefs.edit();
ed.clear();
ed.commit();
}
public static void deleteDataByKey(String prefName, String key) {
SharedPreferences prefs = context.getApplicationContext().getSharedPreferences(prefName, Context.MODE_PRIVATE);
SharedPreferences.Editor ed = prefs.edit();
ed.remove(key);
ed.commit();
}
public static boolean dataExists(String prefName, String key) {
SharedPreferences prefs = context.getApplicationContext().getSharedPreferences(prefName, Context.MODE_PRIVATE);
return prefs.getAll().containsKey(key);
}
}
init your cache by calling JSONCache.init and passing the context (this is normally the activity you are on)
Now whenever you need to save just call:
JSONCache.saveData("myPreference", keyForSave, jsonStringToSave);
To Fetch from cache
String json=JSONCache.loadData(myPreference, jsonStringToSave)
you can then use the same json converter from before to turn your json string back into a list.
I guess database would be an overkill, you can simply write the mail addresses to a file or save them in the SharedPreferences you can read about different storages here: http://developer.android.com/guide/topics/data/data-storage.html

Maximum length of Intent putExtra method? (Force close)

I need some help with debugging my application. First of all: In emulator and on some other devices my app is running fine. On my device I got a force close (without a force close message).
The "crash" happens if the Activity of the app is changed.
Here is some code of the MainActivity class. It just reads html content from a web page over webview. And no, it is NOT possible to do this over HttpRequest because I was not able to simulate the post request.
public class MainActivity extends Activity {
public final static String EXTRA_HTML = "com.example.com.test.HTML";
private WebView mWebView;
private ProgressDialog mDialog;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mWebView = (WebView) findViewById(R.id.webView1);
CookieSyncManager.createInstance(this);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
mWebView.setBackgroundColor(0);
mWebView.setWebChromeClient(new WebChromeClient() {
public boolean onConsoleMessage(ConsoleMessage cmsg) {
if (cmsg.message().startsWith("MAGIC")) {
mDialog.cancel();
/*HashMap<String, String> message = new HashMap<String, String>();*/
String msg = cmsg.message().substring(5);
Intent intent = new Intent(MainActivity.this,
ReadDataActivity.class);
/*message.put("message", msg);*/
/*intent.putExtra(EXTRA_HTML, message);*/
intent.putExtra(EXTRA_HTML, msg);
startActivity(intent);
}
return false;
}
});
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setPluginState(PluginState.OFF);
mWebView.getSettings().setLoadsImagesAutomatically(false);
mWebView.getSettings().setBlockNetworkImage(true);
mWebView.getSettings().setAppCacheEnabled(true);
mWebView.getSettings().setSavePassword(true);
mWebView.getSettings()
.setCacheMode(WebSettings.LOAD_NORMAL);
mWebView.setWebViewClient(new WebViewClient() {
public void onPageFinished(WebView view, String address) {
if (address.indexOf("mySession") != -1) {
view.loadUrl("javascript:console.log('MAGIC'+document.getElementsByTagName('html')[0].innerHTML);");
}
});
mWebView.loadUrl("http://www.myurl.de");
}
So, in the onConsoleMessage() method I just pass the html code to another Activity class which read, parse and display the content.
The problem is now that at this point when the ReadDataActivity class should be loaded the application just close and go back to the home screen without any message or user dialog.
Is it possible that the html code which is passed as a string to the ReadDataActivity is to big? I also try to add the html code as a string in a HashMap but the problem is the same.
Some ideas what I can do to debug the problem? Maybe I should try to create a Parcelable object?
In the emulator everything is working fine.
As per my experience (sometime ago), you are able to parcel up to 1MB of data in a Bundle for IPC. This limit can be reduced if a lot of transactions are happening at a given time. Further information here.
In order to overcome this issue, I would suggest you to save your content on a temp file and pass the path/URI of your temp file to your second activity. Then in your second activity, read the contents out from file, perform your desired operations and finally delete that file.
If you want, you may also incorporate Shared_Preferences for this task - if you think handling files is cumbersome.
I did some research on the maximum amount of data you can transfer using an Intent. And it seems that the limit is nowhere near 1MB or 90KB, it's more like 500KB (tested on API 10, 16, 19 and 23).
I wrote a blog post about this topic, you can find it here: http://web.archive.org/web/20200217153215/http://neotechsoftware.com/blog/android-intent-size-limit
The size limit of Intent is still pretty low in Jelly Bean, which is somewhat lower than 1MB (around 90K), so you should always be cautious about your data length, even if your application targets only latest Android versions.
I have seen that by writing and reading from a file consists of less performance .
Then I have seen this solution : . So I am using this solution :
public class ExtendedDataHolder {
private static ExtendedDataHolder ourInstance = new ExtendedDataHolder();
private final Map<String, Object> extras = new HashMap<>();
private ExtendedDataHolder() {
}
public static ExtendedDataHolder getInstance() {
return ourInstance;
}
public void putExtra(String name, Object object) {
extras.put(name, object);
}
public Object getExtra(String name) {
return extras.get(name);
}
public boolean hasExtra(String name) {
return extras.containsKey(name);
}
public void clear() {
extras.clear();
}
}
Then in MainActivity I have called it like the following :
ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
extras.putExtra("extra", new byte[1024 * 1024]);
extras.putExtra("other", "hello world");
startActivity(new Intent(MainActivity.this, DetailActivity.class));
and in DetailActivity
ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
if (extras.hasExtra("other")) {
String other = (String) extras.getExtra("other");
}
The fixed size of 1MB is not only limited to intents. As Intents, Content Providers, Messenger, all system services like Telephone, Vibrator etc. utilize IPC infrastructure provider by Binder. Moreover the activity lifecycle callbacks also use this infrastructure.
1MB is the overall limit on all the binder transactions executed in the system at a particular moment.
In case there are lot of transactions happening when the intent is sent,it may fail even though extra data is not large.
http://codetheory.in/an-overview-of-android-binder-framework/
A little late to the game, but I just ran up against the same issue. Writing the data to file didn't really make sense performance-wise in my case, but I came across this in my search for answers:
http://developer.android.com/guide/faq/framework.html#3
Using a singleton is better for me as there's no need for disk IO. Better performance if the data doesn't need to be persisted.
Here's an example implementation:
public class DataResult {
private static DataResult instance;
private List<SomeObject> data = null;
protected DataResult() {
}
public static DataResult getInstance() {
if (instance == null) {
instance = new DataResult();
}
return instance;
}
public List<SomeObject> getData() { return data; }
public void setData(List<SomeObject> data) { this.data = data; }
}
Then you can set using this in one activity:
DataResult.getInstance().setData(data);
And get it in the other activity like this:
List<SomeObject> data = DataResult.getInstance().getData();
The Binder transaction buffer has a limited fixed size - 1Mb.
But the problem is that buffer shared by all transactions in progress for the process.
So try to keep your intent's data as small as possible every time.
The use of static String variable is good. If there is a need for the user to go back & forth between different pieces of HTML, you can also use LruCache like this:
static LruCache<String, String> mMemoryCache;
final int kiloByte = 1024;
.
.
final int maxMemoryKB = (int) (Runtime.getRuntime().maxMemory() / kiloByte);
// then choose how much you want to allocate for cache
final int cacheSizeKB = maxMemoryKB / 8;
.
.
mMemoryCache = new LruCache<String, String>(cacheSizeKB) {
//#Override
protected int sizeOf(String key, String value) {
try {
byte[] bytesUtf8 = value.getBytes("UTF-8");
return bytesUtf8.length / kiloByte;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return -1;
}
};
.
.
String cacheKey = generateUniqueString(key);
if (mMemoryCache.get(key) == null) {
mMemoryCache.put(cacheKey, yourContent);
}
Intent intent = new Intent(getApplicationContext(), ReadDataActivity.class);
intent.putExtra(EXTRA_HTML, cacheKey);
startActivity(intent);
Then on the ReadDataActivity side
Intent intent = getIntent();
String cacheKey = intent.getStringExtra(EXTRA_HTML);
String contentString = MainActivity.mMemoryCache.get(cacheKey);
doSomethingWith(contentString);
This idea came from here.
An alternative solution for passing large data between activities is to use a static field. In your case add this line to ReadDataActivity class
public static String msg;
Then you can use the static field msg within MainActivity class as follows
ReadDataActivity.msg = cmsg.message().substring(5);
And finally start your activity without extra put
Intent intent = new Intent(MainActivity.this, ReadDataActivity.class);
startActivity(intent);

Categories

Resources