I have a custom object class Record that implements Parcelable and I'm creating a ListView via ArrayAdapter<Record> I want to be able to save that list so that it automatically loads the next time the user opens the app. The list is populated dynamically and I'm calling my save method everytime a record is added. Then I have a SharedPreference with a boolean value that I set to true so that I know the user has saved some data to load the next time the app is open. Here are my save and load methods:
public void writeRecordsToFile(ArrayAdapter<Record> records) {
String fileName = Environment.getExternalStorageDirectory().getPath() + "/records.dat";
try {
File file = new File(fileName);
if(!file.exists()){
file.createNewFile();
}
ObjectOutputStream stream = new ObjectOutputStream(new FileOutputStream(file));
stream.writeObject(records);
stream.flush();
stream.close();
}
catch (IOException e){
Log.e("MyApp","IO Exception: " + e);
}
writeSavedState();
}
the writeSavedState() is for my SP
public void readRecordsList() {
String fileName = Environment.getExternalStorageDirectory().getPath() + "/records.dat";
try {
ObjectInputStream inputStream = new ObjectInputStream(getApplicationContext().openFileInput(fileName));
adapter = (ArrayAdapter<Record>)inputStream.readObject();
inputStream.close();
}
catch (Exception e){
Log.e("MyApp" , "File Not Found: " + e);
}
}
When I first open the app I get a message:
E/MyApp﹕ File Not Found: java.lang.IllegalArgumentException: File /storage/emulated/0/records.dat contains a path separator
and then when I add a Record to my list I get the message:
E/MyApp﹕ IO Exception: java.io.IOException: open failed: EACCES (Permission denied)
The second message I'm assuming I'm getting because of the first message. This is my first time working with I/O in Android so any help would be appreciated!
EDIT
After adding the permissions to the manifest I'm now only getting an error:
E/MyApp﹕ IO Exception: java.io.NotSerializableException: android.widget.ArrayAdapter
As I said, my custom object is Parcelable and the rest of this is being done in my MainActivity. Do I need to make a new class that is Serializable to build my ArrayAdapter?
I would suggest to save the records in internal storage in private mode,which can be accessed by your app only.If you store it in External storage, there is no guarantee that it will be available next time you load your app.
Also, you should save array of record objects rather than ArrayAdapter object.
Parcel and Parcelable are fantastically quick, but its documentation says you must not use it for general-purpose serialization to storage, since the implementation varies with different versions of Android (i.e. an OS update could break an app which relied on it). So use Serializable in this case instead of Parcalable (from this SO thread)
You can use global variables to pass data from one activity to another. Also you can read/ write records when app starts using global class which extends Applicaion class.
You can try following code,
public class GlobalClass extends Application {
public static Object objectToBePassed; // global variable
final static private RECORDS_FILENAME = "myRecords.txt"
#Override
public void onCreate(){
super.onCreate();
readRecordsFromFile(); // read records when app starts
}
public boolean writeRecordsToFile(ArrayList<Record> records){
FileOutputStream fos;
ObjectOutputStream oos=null;
try{
fos = getApplicationContext().openFileOutput(RECORDS_FILENAME, Context.MODE_PRIVATE);
oos = new ObjectOutputStream(fos);
oos.writeObject(records);
oos.close();
return true;
}catch(Exception e){
Log.e(getClassName(), "Cant save records"+e.getMessage());
return false;
}
finally{
if(oos!=null)
try{
oos.close();
}catch(Exception e){
Log.e(getClassName(), "Error while closing stream "+e.getMessage());
}
}
}
private boolean readRecordsFromFile(){
FileInputStream fin;
ObjectInputStream ois=null;
try{
fin = getApplicationContext().openFileInput(RECORDS_FILENAME);
ois = new ObjectInputStream(fin);
ArrayList<Record> records = (ArrayList<Record>) ois.readObject();
ois.close();
Log.v(getClassName(), "Records read successfully");
return true;
}catch(Exception e){
Log.e(getClassName(), "Cant read saved records"+e.getMessage());
return false;
}
finally{
if(ois!=null)
try{
ois.close();
}catch(Exception e){
Log.e(getClassName(), "Error in closing stream while reading records"+e.getMessage());
}
}
}
}
So to pass any object from activity A to activity B, use following code in activity A ,
Intent intent = new Intent(this,B.class);
GlobalClass.objectToBePassed = obj;
startActivity(intent);
in activity B,
MyClass object = (MyClass) GlobalClass.objectToBePassed;
so to pass a Record class object, replace MyClass with Record.
Related
I am trying to save in cache response from server for certain time.
There are tne next data for saving in cache: I have a List<ProgrammeItem> which I am getting from server. While user is working, he can download up to ~230 List<ProgrammeItem> (but it is unreal to reach this, estimated is 10-50).
ProgrammeItem oblect including strings, int, int[].
That is how I am saving and getting the last downloaded List<ProgrammeItem>:
//saving / getting Programme items
public boolean saveObject(List<ProgrammeItem> obj) {
final File suspend_f=new File(android.os.Environment.getExternalStorageDirectory(), "test");
FileOutputStream fos = null;
ObjectOutputStream oos = null;
boolean keep = true;
try {
fos = new FileOutputStream(suspend_f);
oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
} catch (Exception e) {
keep = false;
Log.e("catching exception ", "" + e.getMessage() + ";;;" + e);
} finally {
try {
if (oos != null) oos.close();
if (fos != null) fos.close();
if (keep == false) suspend_f.delete();
} catch (Exception e) { /* do nothing */ }
}
return keep;
}
public List<ProgrammeItem> getObject(Context c) {
final File suspend_f=new File(android.os.Environment.getExternalStorageDirectory(), "test");
List<ProgrammeItem> simpleClass= null;
FileInputStream fis = null;
ObjectInputStream is = null;
try {
fis = new FileInputStream(suspend_f);
is = new ObjectInputStream(fis);
simpleClass = (List<ProgrammeItem>) is.readObject();
} catch(Exception e) {
String val= e.getMessage();
} finally {
try {
if (fis != null) fis.close();
if (is != null) is.close();
} catch (Exception e) { }
}
return simpleClass;
}
That is how I am saving and getting object in activity:
PI = new ProgrammeItem();
List<ProgrammeItem> programmeItems = new ArrayList<>();
...
//filling programmeItems with data from server
...
boolean result = PI.saveObject(programmeItems); //Save object
ProgrammeItem m = new ProgrammeItem();
List<ProgrammeItem> c = m.getObject(getApplicationContext()); //Get object
The question is: how can I save a lot of my objects instead of only one?
I think I should done something like public boolean addObjectsInCache(List<ProgrammeItem> obj) for adding objects, not overriding them.
And change get method into public List<ProgrammeItem> getObject(Context c, String id), where id will be unique identifier, which will includes into every ProgrammeItem in the every List<ProgrammeItem>.
Am I right? And how I can achieve this? Maybe you will show me the other way to work with objects and cache?
You can use SharedPreference instead, while having a local database Android Room can also be an option. SharedPreference basically is stored in your device's cache while the local database is stored in your device's data hence in our apps we have clear cache and clear data function.
Additional Resources:
StackOverFlow: How Android SharedPreferences save/store object
Object based preference library: https://github.com/ShawnLin013/PreferencesManager I would suggest you go with this one, since it can easily save you time saving list based object and retrieving them. You can also add more to the persisted list object when needed.
Secured Preferences: https://github.com/scottyab/secure-preferences
An option could be to use Room database with inMemoryDatabaseBuilder:
db = Room.inMemoryDatabaseBuilder(context, ProgrammeDatabase::class.java)
.build()
if it all can fit in memory.
I am currently making an app for our group in high school. We decided to make a personal financing app that organizes your spendings.
I am trying to use internal storage for storing data. I have created an InternalStorage class just for that (it was someone else's answer to a question like mine but I forgot from where. Whoops.), having write and read methods accordingly.
However, while debugging I found some implausible behavior.
public final class InternalStorage {
private static String key = "billify";
public static void write(Context context, List<Bill> billList){
try{
FileOutputStream fos = context.openFileOutput(key, Context.MODE_PRIVATE);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(billList);
oos.close();
fos.close();
}catch(IOException e){
Toast.makeText(context,"Internal storage write error: IOException", Toast.LENGTH_LONG).show();
}
}
public static List<Bill> read(Context context){
try {
FileInputStream fis = context.openFileInput(key);
ObjectInputStream ois = new ObjectInputStream(fis);
List<Bill> a = (List<Bill>) ois.readObject();//jumps from here
return a;
}catch(Exception e){
e.printStackTrace();
Toast.makeText(context,"Internal storage read error",Toast.LENGTH_LONG).show();
return null;//jumps to here w/o triggering previous two lines.
}
}
}
I have put breakpoints in all the lines of code from read(), and from what I saw, the code ran ois.readObject(), then without running e.printStackTrace() and Toast, jumped straight back to return null.
Does anybody know what's going on?
Edit:
if(z.getClass() == cls){
ArrayList<Bill> a = (ArrayList<Bill>) z;//Jumps from here now
return a;
}
return null;
Doing a checked cast still makes the same thing happen-the classes are same, but doing the cast still makes it jump to the previous, last null.
I have a Hashmap in a Fragment variable for a Position(int) to View Relationship.
sometimes I can read the Object in onCreate but with no data ( size 0) , However when I want to store it when onPause or onStop fires of the fragment to internal storage , it gives me a weird error without any clue what cause it:
android.widget.RelativeLayout
Fragment onPause:
#Override
protected void onPause() {
super.onPause();
try
{
FileOutputStream fileOutStream = getContext().openFileOutput("hashMap", Context.MODE_PRIVATE);
ObjectOutputStream objOutStream = new ObjectOutputStream(fileOutStream);
objOutStream.writeObject(HashMap);
objOutStream.close();
Log.v("Fragment:", "Done Writing "+HashMap.size());
}catch (IOException e)
{
Log.v("Fragment:","MainPage(onStop): "+e.getMessage()+e.getLocalizedMessage());
}
}
the following lines to read from internal storage when opening the app.
try {
FileInputStream f = getContext().openFileInput("hashMap");
ObjectInputStream s = new ObjectInputStream(f);
HashMap= (HashMap<Integer, View>) s.readObject();
s.close();
Log.v("Fragment:", "Done Reading " + HashMap.size());
} catch (ClassNotFoundException | IOException e) {
Log.v("Fragment:", "MainPage(onCreate): " + e.getMessage());
}
I think
Only objects that support the java.io.Serializable interface can be written to streams
according to ObjectOutputStream
The View does not seem to implement this interface.
Hi Iam having serious issues try to persist some serializable objects to a file on the local android file system. Iam getting a Bad file descriptor error and I think it is to do with my methods for creating the file. the file and checking if the file exists. i create a private file object in the class. Then, on write or read. I check file existance with the following code.
#Override
public boolean fileExists() {
File file = context.getFileStreamPath(filename);
return file.exists();
}
this doesnt instantiate my file object called "objectfile"!! but does check the "filename" exists.
to create the file I call this method if "filename" doesnt exist.
public void createFile()
{
objectfile = new File(context.getFilesDir(), filename);
objectfile.setReadable(true);
objectfile.setWritable(true);
}
Iam not sure if this will give me back my previously created file which would be ideally what I want to do. Is there a way i can just get the old file or create a new one and pass it to "objectfile" variable in the constructor??
Iam also wondering what the best way to do this is??
Or should i just use the mysqlite db? using object file persistance doesn't seem to be working out for me right now and iam working to a deadline. Also this method is mention in the gooogle docs so I thought it would be legit was to do it.
http://developer.android.com/training/basics/data-storage/files.html
here is my method for reading the serializable objects
public synchronized ArrayList<RoomItem> readObjects() {
final ArrayList<RoomItem> readlist = new ArrayList<>();
if(!fileExists())
return readlist;
if(objectfile == null)
createFile();
try {
finputstream = new FileInputStream(objectfile);
instream = new ObjectInputStream(finputstream);
readwritethread = new Thread(new Runnable() {
#Override
public void run() {
try {
final ArrayList<RoomItem> readitems = (ArrayList<RoomItem>) instream.readObject();
instream.close();
finputstream.close();
handler.post(new Runnable() {
#Override
public void run() {
listener.updateList(readitems);
}
});
} catch(IOException e)
{
e.printStackTrace();
}
catch(ClassNotFoundException e)
{
e.printStackTrace();
Log.d("read failed", "file read failed");
}
}
});
}
catch(Exception e)
{
e.printStackTrace();
}
timeOutReadWrite(readwritethread);
readwritethread.start();
try {
readwritethread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d("read from file", "file read");
return readlist;
if anyone could suggest any improvements id really appreciate it. I use a handler to pass back to my activity and implement a listener interface on my activity thats call the activity when all the obj are read. Thanks again!
1#: Yes, it will return the original file you created.
2#: Depends on the thing you want to store, seems File is more flex from description
hope helpful.
We have used
FileOutputStream fos = context.openFileOutput("file.ser", Context.MODE_PRIVATE);
to write our serialized files.This will carete files in /data/data/app.package.name/files/. In fact, this path is returned by getFilesDir().
And while deserializing, use
//make sure you pass the same file that was passed to openFileOutput()..
FileInputStream fis = context.openFileInput("file.ser");
Also, to avoid confusing between file names you can use name of class that is being serialized.
Ex:
public static <T> void serialize(final Context context, final T objectToSerialize) {
....
....
Strin fileName = objectToSerialize.getClass().getSimpleName();
...
}
Do this and keep the method in util so it can be used for any type of objects (T type) to serialize.
At first I have strong Java knowledege, but however just started with Android.
My Android app is downloading some fairly complex data (texts, dates, images) which I am saving in a custom object. The data need to be refresh from time to time. However usually the data downloaded will not change.
In order to keep the data in memory I am using the Application Object. Unfortunately, it looks like the application object instance is destroyed when the app is killed.
Hence, I was wondering if it would be of good practice to serialize and save my custom object (which is contained in the application object) in the internal storage during onPause(). Obviously, I would then first read from the file in onResume() before reloading from the internet. The idea is also to enable offline viewing.
In longer term the plan is to move the code downloading the date in a background service. As there seems to be many different ways to keep application state in Android, I would like to be be sure that this is the correct way to go.
Try using those methods class to save the Object(s) (implements serialize) you need:
public synchronized boolean save(String fileName, Object objToSave)
{
try
{
// save to file
File file = new File(CONTEXT.getDir("filesdir", Context.MODE_PRIVATE) + "/file.file");
if (file.exists())
{
file.delete();
}
file.getParentFile().mkdirs();
file.createNewFile();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(objToSave);
oos.close();
return true;
}
catch (FileNotFoundException e)
{
e.printStackTrace();
return false;
}
catch (IOException e)
{
e.printStackTrace();
return false;
}
}
public synchronized Object load(String fileName)
{
try
{
File file = new File(CONTEXT.getDir("filesdir", Context.MODE_PRIVATE) + "/file.file");
if (!file.exists())
{
return null;
}
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
savedObj = ois.readObject();
ois.close();
return savedObj;
}
catch (FileNotFoundException e)
{
e.printStackTrace();
return null;
}
catch (Exception e)
{
e.printStackTrace();
return null;
}
}
You'll need to cast the Object you load().
CONTEXT is an Activity or ApplicationContext to get access to the cachedir.
Your could use Environment.getExternalStorageState() instead to get a directory path. DOn't forget to add it "/filename".