Android: Custom Parcelable Class Crashes App Without Throwing Exception - java

I've got an Android app with custom objects which implement the Parcelable interface. They way I have it set it up is that my program initially creates an ArrayList of a custom class Products from a file in the bundle. I can see and confirm that the arraylist and it's instance variabels are populated appropriately. This class has several instance variables along with one being another ArrayList but with the String class. Remember that fact.
I am trying to pass the ArrayList<Product> into a new activity like so:
try {
Intent i = new Intent(RootActivity.this, ProductsActivity.class); //Intent from this activity to the next
i.putParcelableArrayListExtra("Products", app_products); //Puts my ArrayList<Class A> as an extra
startActivity(i); //Launch the activity
}
catch(Exception e){
Log.d("Activity Error", "Error Here:" + e.getMessage());
}
I am collecting the information back from the intent in my new activity by pulling the ArrayList out by using
app_products = getIntent().getParcelableArrayListExtra("Products");
For my custom class, it looks something like this, along with the implemented Parcelable methods.
public class Product implements Parcelable{
private String name;
private String cost;
private ArrayList<String> similarItems;
public Product{
name = null;
cost = null;
similarItems = new ArrayList<String>();
}
public Product(String name, String cost){
this();
this.name = name;
this.cost = cost;
}
public addSimilarItem(String item){
similarItems.add(item);
}
public static final Parcelable.Creator<Product> CREATOR
= new Parcelable.Creator<Product>()
{
public Product createFromParcel(Parcel in) {
return new Product(in);
}
public Product[] newArray(int size) {
return new Product[size];
}
};
public int describeContents(){
return 0;
}
private Product(Parcel in){
name = in.readString();
cost = in.readString();
similarItems = in.readArrayList(String.class.getClassLoader());
}
public void writeToParcel(Parcel out, int flags){
out.writeString(name);
out.writeString(cost);
out.writeList(similarItems);
}
}
So this works well WITHOUT my String arraylist being added in the class
Comment out out.writeList(similarItems); and also similarItems = in.readArrayList(String.class.getClassLoader());
but once you add them back in into the class, the app crashes but it doesn't even throw a message for debugging. I've wrapped everything around try-catch statements and android doesn't even report the app crashed with the normal dialog on the springboard. I am truly at a loss.
It is worth mentioning that I've used some log statements to understand where the program is crashing despite the fact that android wont throw an exception. I can see that all of the items in my ArrayList undergoes the writeToParcelMethod and completes writing. The Product(Parcel in) method is never called. Lastly, I can also see the class I am launching the new activity from enters the Pause State and my new Activity is never created.
Let me know if I can provide any additional information.

Fairly certain your problem is the use of writeList(). writeList() seems to indicate that it follows the same contract as writeValue() for the items contained in the list. readList() however, seems to indicate that the values must be Parcelable (which String is not).
Either way, typically these calls have to be very specifically linked to their inverse (e.g. writeString() must be read in with readString(), not readValue()) so you should instead use the provided methods for reading/writing String Lists:
// Takes in a List<String> which may be null
out.writeStringList(similarItems);
// Returns either null or a new ArrayList<String> with the contents
similarItems = in.createStringArrayList();

These seemed to be due to some malformed XML which my app uses as a resource. Not sure why this was this issue but after many hours of hunting it down, I was able to remove the bad XML and will revisit this issue at a later date towards when I need to release the app.
Right now, I'm just gonna worry about continuing to develop it. I'll try to remember to check back here if I find anything interesting about my XML.

Related

How to access array from different activity?

Yes I'm really new to android and I'm wokring on a very simple app.
On my mainActivity I'm creating and array, and want to access the array from a different activity.
public class Activity extends {
MyAreas[] myArea;
#Override
protected void onCreate(Bundle savedInstanceState) {
myArea= new MyAreas[2];
myArea[0] = new MyAreas(33, 44, "Location ", "active");
myArea[1] = new MyAreas(32, 434, "Location 2", "active");
Class
public class MyAreas{
public double val;
public double val2;
public String name;
public String status;
public MyAreas(double val, double val2, String name, String status) {
this.val= val;
this.val2= val2;
this.name = name;
this.status = status;
}
I'm trying to access myarea array from my activity2.java, I tried this but didn't work.
private ArrayList<MyAreas> mMyAreasList;
Using Parcelable to pass data between Activity
Here is answer which should help.
In regular Java you can use getters to obtain objects or any variable from a different class. Here is a good article on encapsulation.
In Android, there is a class called Intent that lets you start one activity from another and pass any necessary information to it. Take a look at the developer docs and this other answer which should help you.
For your begginer level, rather than using intents, just set the array object public and static, like that:
public static MyAreas[] myArea;
By that way you can access it from any activity in your app..
Then, go to the activity2.java wherever you want to access it.
MyAreas area = Activity.myArea[0];
The problem with this approach is that you do not have complete control when and in what order the activities are created or destroyed. Activities are sometimes destroyed and automatically restarted in response to some events. So it may happen that the second activity is started before the first activity, and the data is not initialized. For this reason it is not a good idea to use static variables initialized by another activity.
The best approach is to pass the data via the intent. The intents are preserved across activity restarts, so the data will be preserved as well.
Another approach is to have a static field to keep the data, and initialize the data in an Application instance.

Android Java - Race Condition in OnCreate with two Observers and making lists

sorry if this is a convoluted question. Working on creating an app for a college course and I'm running into (what appears to be) a race condition in my OnCreate method.
TL;DR - sometimes my spinner populates and I can get an index from it. Sometimes it's not populated yet when trying to get a specific index. Details and code below.
The app is a "course scheduler" for a college student.
I'm creating an Activity that displays existing course information and allows you to edit it. In the OnCreate method for this Activity, I am filling a spinner for "Mentors" for the course and a spinner for which "Term" the course belongs in. This information is being pulled from a Room DB.
I have a seperate activity for a new course and for editing a course. For the "new course" activity, everything works fine. I getAllMentors() or getAllTerms() successfully and fill the spinner list.
For the "Edit Course" Activity, there's an extra step involved and it seems to be causing me some issues.
When editing a course, I pass the intent from the originating Activity with all the necessary EXTRAS. This is successful.
In OnCreate for EditCourseActivity, I do the following:
I get the mentorID from the EXTRA that's passed in from the originating Activity.
I access my MentorViewModel and call my getAllMentors() method which returns LiveData> of all mentors in the db.
because it returns LiveData, I use an observer and loop through the LiveData adding the Name of each mentor to a List and the
entire mentor to a List.
I populate my spinner with the information in List full of mentor names.
then I do a for loop, looping through List looking for one that has the same id as what I grabbed form the EXTRA in step 1.
If I find a match in that list, I call a getMentorName() method to snag their name as a string.
I have a methond getIndex(spinner, string) that will loop through the provided spinner, trying to find a match for the string that's
passed in (mentors name) that I grabbed that should match the ID of
the mentor assigned to the course. This method returns index location
of the matched string in the spinner.
I set the spinner selection to the index found.
I do basically the same process for term.
Me being a new developer, I'm not used to OnCreate running the code synchronously.
Because of this, it appears that I have a race condition somewhere between populating the List of mentor names that populates the spinner, and calling my getIndex() method.
Sometimes the spinner is populated and getIndex works properly and sets the correct mentor. Sometimes the spinner is empty and my getIndex() returns -1 (which it should do in a no-find situation) that populates the spinner with the first item in the list (once it's populated).
protected void onCreate(Bundle savedInstanceState) {
//////////////////////////Handling Mentor spinner menu/////////////////////////////////////////////////
int mentorId = courseData.getIntExtra(EXTRA_COURSE_MENTOR_ID, -1);
final ArrayAdapter<String> sp_CourseMentorAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, mentorNameList);
sp_CourseMentorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sp_CourseMentor.setAdapter(sp_CourseMentorAdapter);
final MentorViewModel mentorViewModel = ViewModelProviders.of(this).get(MentorViewModel.class);
//Mentor test = mentorViewModel.getMentorById(mentorId);
mentorViewModel.getAllMentors().observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
if (mentorList != null) {
for (Mentor m : mentorList) {
mentorNameList.add(m.getMentor_name());
mentorListMentor.add(m);
}
}
sp_CourseMentorAdapter.notifyDataSetChanged();
}
});
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
Is there a way to get them to run asynchronously? Somehow to get the observer doing my getAllMentors() to complete first and populate the spinner, THEN have the for loop run?
Or a better way to handle this?
Thanks in advance.
Room always runs the code on a separated thread, not the Main/UI thread. You can change that behavior with
allowMainThreadQueries()
after initializating your database. This will make the query run first, populate your list and then run your for-loop code. I do not recommend this approach, since it is a bad practice to make queries on the UI thread.
You have two options:
Change your foor loop to a function and call it after adding the values from the observer:
mentorViewModel.getAllMentors().observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
if (mentorList != null) {
for (Mentor m : mentorList) {
mentorNameList.add(m.getMentor_name());
mentorListMentor.add(m);
}
lookForMentor();
}
}
});
private void lookForMentor() {
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
}
Put the for inside the observer, change the Room DAO to return a List and use LiveData on your own viewmodel:
MentorViewModel.java:
MentorViewModel extends ViewModel {
private MutableLiveData<List<Mentor>> _mentorsLiveData = new MutableLiveData<List<Mentor>>();
public LiveData<List<Mentor>> mentorsLiveData = (LiveData) _mentorsLiveData;
void getAllMentors(){
//room db query
_mentorsLiveData.postValue(mentorsList);
}
}
EditActivity.java:
mentorsViewModel.getAllMentors();
mentorViewModel.mentorsLiveData.observe(this, new Observer<List<Mentor>>() {
#Override
public void onChanged(#Nullable List<Mentor> mentorList) {
mentorsListMentor.addAll(mentorList);
sp_CourseMentorAdapter.notifyDataSetChanged();
for(Mentor m: mentorListMentor){
if (m.getMentor_id()==mentorId){
String test = m.getMentor_name();
int spinnerSelectionM2 = getIndexM(sp_CourseMentor, test);
sp_CourseMentor.setSelection(spinnerSelectionM2);
}
}
}
}
});

global variables between different classes java

i am on the creation of an app in android. its a calculator app. the main activity is where the user could input the equation, and the second activity is where the user can add/edit/delete variables. so i made a new class in another file named Global.java. then i extended it to application, imported everything i need, made s private string, made some public functions, edited the manifest, and initialized it right on my main activity. everything works fine while im only using a string to be passed by the functions but when i started adding what i need, an ArrayList, and made some functions so i could access the list then run it, the app closes. i think its because the arraylist is not allowed to be passed to different classes? am i right or am i just missing something?
please dont downvote my post if i didn't post something needed. i am using aide so there is no log output. code:
Global.java
...
import android.app.*;
import java.util.*;
public class Global extends Application
{
private String s;
public static ArrayList<String> sList;
public String getS() {
return s;
}
public void setS(String ss) {
s=ss;
}
public void add() {
sList.add(s);
}
}
MainActivity.java
...
String s;
...
global=(Global)getApplicationContext();
...
global.setS("jian"); //this one works
global.sList.add("jian"); // this one dont
...
Are you sure you initialized sList, like this:
sList = new ArrayList<String>();
If you didn't, you might want to change its declaration to include this initialization.
public static ArrayList<String> sList = new ArrayList<String>();
Just do
global.add("jian");
since you have an add function to take care of the addition of item to arraylist.
Also, try with this:
public void add(String ss) {
sList.add(ss);
}
You are not instantiating your arraylist.
public static ArrayList<String> sList = new Arraylist<String>();
Also you should read beginner tutorials on Java and android, using a public extension of application like this is a bad idea and you can get log outputs from different apps if Aide doesn't provide that, search play store

How to pass a custom class between Activities

I found many simple solutions to this (such as Intent.putExtra(String, String) and Bundle.putString(String, String)), but this is not helpful for my situation.
I have a class called MyMP3 which contains non-primitive types. I need to pass the following for MyMP3...
private AudioFile audioFile;
private Tag tag;
private int index;
private boolean saved, startedWithLyrics;
private String id3lyrics;
AudioFile and Tag are both classes that I imported from a .jar file. How can I go about passing these to another Activity via Intents? I tried messing with implementing Parcelable for my "MyMP3" class, but I am not sure how to correctly use these methods when not passing primitive types.
Could you help me out and look at my code below and try to tell me how to correctly use Parcelable with a custom class like mine? How do I set the Parcel in the writeToParcel function and how do I correctly retrieve the class in another Activity?
Below is my code (the part that is important, at least). I've been trying different things for a couple of days now, but I cannot get it to work. Please help me out!
public class MyMP3 extends AudioFile implements Parcelable
{
private AudioFile audioFile;
private Tag tag;
private int index;
private boolean saved, startedWithLyrics;
private String id3lyrics;
public MyMP3(File f, int index)
{
this.audioFile = AudioFileIO.read(f);
this.tag = this.audioFile.getTag();
this.index = index;
this.saved = false;
this.id3lyrics = getLyrics();
}
#Override
public int describeContents()
{
return 0;
}
#Override
public void writeToParcel(Parcel out, int flats)
{
/* This method does not work, but I do not know how else to implement it */
Object objects[] = {this.audioFile, this.tag, this.index, this.saved, this.startedWithLyrics, this.id3lyrics};
out.writeArray(objects);
}
public static final Parcelable.Creator<MyMP3> CREATOR = new Parcelable.Creator<MyMP3>()
{
public MyMP3 createFromParcel(Parcel in)
{
/* Taken from the Android Developer website */
return new MyMP3(in);
}
public MyMP3[] newArray(int size)
{
/* Taken from the Android Developer website */
return new MyMP3[size];
}
};
private MyMP3(Parcel in)
{
/* This method probable needs changed as well */
Object objects[] = in.readArray(MyMP3.class.getClassLoader());
}
}
You can make your MyMP3 class Parcelable like that. Make sure you get the read/write order correct. The non-primitives must also be Parcelable, so you might not have control over that unfortunately. Alternatively, you could come up with your own serialization/deserialization. You could use a text format, like JSON or XML. Another alternative is to use subclass Application (make sure you declare it in your manifest) and use it is as a place to hang objects that span Activities. This keeps the object in memory for the lifecycle of your app, so be careful with doing this.

Passing a String Array Between Java Classes Android App

I am writing an Android app where I need to pass a string array between two classes. The string initializes fine and I can output the contents of the string fine in the one class but as I try to pass it to another class I get a Null Pointer Exception error. The following is the stripped down version of my code:
accelerometer.java:
public class accelerometer extends Service {
public String movement[];
public void onCreate() {
movement = new String[1000000];
}
public void updatearray() {
movement[arraypos]=getCurrentTimeString();
//Toast.makeText(this, movement[arraypos] , Toast.LENGTH_SHORT).show(); //this correctly displays each position in the array every time it updates so I know the array is working correctly in this file
arraypos+=1;
}
public String[] getmovement(){
return movement;
}
}
wakeupalarm.java:
public class wakeupalarm extends Activity {
private TextView herestext_;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.wakeup);
herestext_ = (TextView) findViewById(R.id.TextView01);
accelerometer accelerometercall = new accelerometer();
String movearray[] = accelerometercall.getmovement();
herestext_.setText(movearray[2]);
}
}
I have a feeling I'm missing something very simple but any help would be greatly appreciated!
Thanks,
Scott
You're creating a new accelerometer class, which is completely uninitialized since there is no constructor, then you access its member. Of course it'll be null.
Not sure how your two classes are related, but if the activity is called by the service, then you need to pass the string through the intent (through an extra, for example).
Side note: Class names should always start with a capital letter. Method/variable names should have camel case, i.e. "updateArray". Also, you can format your code here by selecting it and pressing CTRL+K.
Your first problem, I think, is that you are creating an array with a million slots in it. Do you really mean to be doing that? It's going to take a lot of memory---quite possibly more than is available. You should instead look to having a Vector of Strings that you extend as necessary.

Categories