android parcelable referencing another parcelable circular dependence
Asked Answered
S

2

8

Rather simple scenario really, but I could not find anything related on Google so here goes:

class ContainerClass implements Parcelable {
  List<ItemClass> _items;
  (...)

  public void writeToParcel( Parcel p, int args ) {
    p.writeList( _items );
    (...)
  }
}

class ItemClass implements Parcelable {
  ContainerClass _containerRef;      
  (...)

  public void writeToParcel( Parcel p, int args ) {
    p.writeParcelable( _containerRef );
    (...)
  }      
}

This will inevitably loop and overflow the stack.

My question: How am I supposed to deal with a situation where I have to pass an object of the above types through to a new Activity.

(For CommonsWare) Parcelable implementation does indeed not seem to check for, and avoid circular dependencies. Stacktrace with classnames replaced by above names:

08-12 10:17:45.233    5590-5590/com.package E/AndroidRuntime: FATAL EXCEPTION: main
        java.lang.StackOverflowError
        at com.package.ContainerClass.writeToParcel(ContainerClass.java:139)
        at android.os.Parcel.writeParcelable(Parcel.java:1254)
        at com.package.ItemClass.writeToParcel(ItemClass.java:182)
        at android.os.Parcel.writeParcelable(Parcel.java:1254)
        at android.os.Parcel.writeValue(Parcel.java:1173)
        at android.os.Parcel.writeList(Parcel.java:622)
        at com.package.ContainerClass.writeToParcel(ContainerClass.java:144)
        at android.os.Parcel.writeParcelable(Parcel.java:1254)
        at com.package.ItemClass.writeToParcel(ItemClass.java:182)
        at android.os.Parcel.writeParcelable(Parcel.java:1254)
        at android.os.Parcel.writeValue(Parcel.java:1173)
        at android.os.Parcel.writeList(Parcel.java:622)
Sprightly answered 11/8, 2013 at 21:24 Comment(0)
S
1

I've been pondering some more and come up with two workarounds useful if anyone else is in the same boat:

1) (Inspired by CommonsWare) Put a flag on each part of the chain to indicate direction Up the heirarchy is lossy in the sense that all items to the ContainerClass cannot be restored.

class ContainerClass implements Parcelable {
  boolean _parcelableDownHeirarchy = true;
  List<ItemClass> _items;
  (...)

  private ContainerClass( Parcel in ) {
    _items = in.readArrayList( ItemClass.class.getClassLoader() );
    (...)

    if ( _parcelableDownHierarchy ) {
      for ( int i = 0; i < _items.size(); i++ ) 
        _items.get( i ).set_container( this );
    }          
  }

  public void writeToParcel( Parcel p, int args ) {
    p.writeByte( (byte)_parcelableDownHierarchy ? 1 : 0 );
    if ( _parcelableDownHeirarchy )
      p.writeList( _items );

    (...)
  }
}


class ItemClass implements Parcelable {
  boolean _parcelableDownHeirarchy = true;
  ContainerClass _containerRef;      
  (...)

  private ItemClass( Parcel in ) {
    if ( !_parcelableDownHeirarchy ) {
      _containerRef = in.readParcelable( ContainerClass.class.getClassLoader() );
      //Won't contain item in it's _items list.          
    }
  }

  public void writeToParcel( Parcel p, int args ) {
    p.writeByte( (byte)_parcelableDownHierarchy ? 1 : 0 );
    if ( !_parcelableDownHeirarchy ) //Up the heirarchy
      p.writeParcelable( _containerRef );

    (...)
  }      
}

2) Hackish workaround, employing a static hash table, granted each object can be uniquely identified by it's parcelable attributes. (In my case I have the primary key in my database in the objects).

class ContainerClass implements Parcelable {
  //Leave be
}


class ItemClass implements Parcelable {
  HaspMap<Long, ContainerClass> _parents = new HashMap<Long, ContainerClass>();
  ContainerClass _containerRef;      
  (...)

  public long get_PKhash() { /* Return unique identifier */ }

  private ItemClass( Parcel in ) {
    (...)
    assertTrue( (_containerRef = _parents.remove( get_PKhash() )) != null );
  }

  public void writeToParcel( Parcel p, int args ) {
    (...)//Don't write _containerRef
    _parents.put( this.get_PKhash, _containerRef );
  }      
}
Sprightly answered 12/8, 2013 at 9:10 Comment(1)
I ended up implementing number 2.Sprightly
C
9

This will inevitably loop and overflow the stack.

AFAIK, the parceling process does not handle circular object graphs. I just filed an issue to get this better documented.

One workaround is to not do p.writeParcelable( _containerRef );. Instead, in ContainerClass, in your ContainerClass(Parcel in) constructor (or however your CREATOR is handling it), after reading in your _items list, iterate over that list and tell each child about its parent.

Cautionary answered 11/8, 2013 at 22:26 Comment(1)
That workaround would be great if I had to a ContainerClass through. Unfortunately it is a collection of ItemClass instances I have to pass to an activity. While I could do the reverse and not p.writeList as I don't really need that information in this part of my program, that implementation causes problems in other parts when "Parseled" ContainerClass instances will not have the _items.Sprightly
S
1

I've been pondering some more and come up with two workarounds useful if anyone else is in the same boat:

1) (Inspired by CommonsWare) Put a flag on each part of the chain to indicate direction Up the heirarchy is lossy in the sense that all items to the ContainerClass cannot be restored.

class ContainerClass implements Parcelable {
  boolean _parcelableDownHeirarchy = true;
  List<ItemClass> _items;
  (...)

  private ContainerClass( Parcel in ) {
    _items = in.readArrayList( ItemClass.class.getClassLoader() );
    (...)

    if ( _parcelableDownHierarchy ) {
      for ( int i = 0; i < _items.size(); i++ ) 
        _items.get( i ).set_container( this );
    }          
  }

  public void writeToParcel( Parcel p, int args ) {
    p.writeByte( (byte)_parcelableDownHierarchy ? 1 : 0 );
    if ( _parcelableDownHeirarchy )
      p.writeList( _items );

    (...)
  }
}


class ItemClass implements Parcelable {
  boolean _parcelableDownHeirarchy = true;
  ContainerClass _containerRef;      
  (...)

  private ItemClass( Parcel in ) {
    if ( !_parcelableDownHeirarchy ) {
      _containerRef = in.readParcelable( ContainerClass.class.getClassLoader() );
      //Won't contain item in it's _items list.          
    }
  }

  public void writeToParcel( Parcel p, int args ) {
    p.writeByte( (byte)_parcelableDownHierarchy ? 1 : 0 );
    if ( !_parcelableDownHeirarchy ) //Up the heirarchy
      p.writeParcelable( _containerRef );

    (...)
  }      
}

2) Hackish workaround, employing a static hash table, granted each object can be uniquely identified by it's parcelable attributes. (In my case I have the primary key in my database in the objects).

class ContainerClass implements Parcelable {
  //Leave be
}


class ItemClass implements Parcelable {
  HaspMap<Long, ContainerClass> _parents = new HashMap<Long, ContainerClass>();
  ContainerClass _containerRef;      
  (...)

  public long get_PKhash() { /* Return unique identifier */ }

  private ItemClass( Parcel in ) {
    (...)
    assertTrue( (_containerRef = _parents.remove( get_PKhash() )) != null );
  }

  public void writeToParcel( Parcel p, int args ) {
    (...)//Don't write _containerRef
    _parents.put( this.get_PKhash, _containerRef );
  }      
}
Sprightly answered 12/8, 2013 at 9:10 Comment(1)
I ended up implementing number 2.Sprightly

© 2022 - 2024 — McMap. All rights reserved.