Android Realm - обновление RealmList вызывает исключение IllegalArgumentException

Я пытаюсь обновить существующий RealmObject (IncidentCard), который включает RealmList типа IncidentPhoto. Объект обновляется без каких-либо проблем, пока я не пытаюсь обновить RealmList, когда я включаю список, я получаю следующее сообщение об ошибке:

E/AndroidRuntime: FATAL EXCEPTION: main
E/AndroidRuntime: Process: com.trollvik.android.incidents247, PID: 31923
E/AndroidRuntime: java.lang.IllegalArgumentException: Each element of 'value' must be a valid managed object.
E/AndroidRuntime:     at io.realm.IncidentCardRealmProxy.setPhotos(IncidentCardRealmProxy.java:218)
E/AndroidRuntime:     at com.trollvik.android.incidents247.activities.EditCardActivity.saveIncidentCard(EditCardActivity.java:155)
E/AndroidRuntime:     at com.trollvik.android.incidents247.activities.EditCardActivity$1.onClick(EditCardActivity.java:95)
E/AndroidRuntime:     at android.view.View.performClick(View.java:5197)
E/AndroidRuntime:     at android.view.View$PerformClick.run(View.java:20926)
E/AndroidRuntime:     at android.os.Handler.handleCallback(Handler.java:739)
E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:95)
E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:145)
E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:5944)
E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:372)
E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1388)
E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1183)

Это класс IncidentCard:

public class IncidentCard extends RealmObject {

    @PrimaryKey
    private long id;

    private String timestamp;
    private String type;
    private RealmList<IncidentPhoto> photos;

    public IncidentCard() {

    }

    public IncidentCard(long id, String timestamp, String type){
    this.id = id;
    this.timestamp = timestamp;
    this.type = type;
    }

    public IncidentCard(long id, String timestamp, String type, RealmList<IncidentPhoto> photos){
    this.id = id;
    this.timestamp = timestamp;
    this.type = type;
    this.photos = photos;
    }

    public long getId() {
    return this.id;
    }

    public void setId(long id) {
    this.id = id;
    }

    public String getTimestamp(){
    return this.timestamp;
    }

    public void setTimestamp(String timestamp) {
    this.timestamp = timestamp;
    }

    public String getType(){
    return this.type;
    }

    public void setType(String type){
    this.type = type;
    }

    public RealmList<IncidentPhoto> getPhotos() {
    return this.photos;
    }

    public void setPhotos(RealmList<IncidentPhoto> photos) {
    this.photos = photos;
    }
}

Это класс IncidentPhoto:

public class IncidentPhoto extends RealmObject {

    private String photoPath;

    public IncidentPhoto() {

    }

    public IncidentPhoto(String photoPath) {
    this.photoPath = photoPath;
    }

    public String getPhotoPath(){
    return this.photoPath;
    }

    public void setPhotoPath(String photoPath){
    this.photoPath = photoPath;
    }
}

Чтобы запросить БД Realm, я создал этот вспомогательный класс:

public class IncidentDbHelper {

    private Realm realm;

    public IncidentDbHelper(Context context) {
    realm = Realm.getInstance(context);
    }

    public void setObject(IncidentCard incidentCard) {
    realm.beginTransaction();
    IncidentCard incident = realm.copyToRealmOrUpdate(incidentCard);
    realm.commitTransaction();
    }

    public IncidentCard getObject(Long id) {
    return realm.where(IncidentCard.class).equalTo("id", id).findFirst();
    }

    public void close(){
    if (realm != null) {
        realm.close();
    }
    }
}

Когда я добавляю новую карточку происшествия, я называю это действие:

public class NewCardActivity extends AppCompatActivity {

    private static final int REQUEST_IMAGE_CAPTURE = 1;
    private static final String INSTANCE_STATE = "currentPhotoPath";
    private static final String INSTANCE_STATE_LIST = "currentPhotoList";

    private Context mContext;

    private IncidentCard mIncidentCard;

    private IncidentDbHelper mDbHelper;
    private IncidentCardId mIncidentId;
    private IncidentCardTimestamp mIncidentTimestamp;
    private RealmConverter mRealmConverter;

    private Resources mRes;
    private PhotoPath mPath;

    private String mCurrentPhotoPath;
    private ArrayList<String> mCurrentPhotoList;



    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mContext = this;
    mDbHelper = new IncidentDbHelper(mContext);
    mIncidentCard = new IncidentCard();
    mRealmConverter = new RealmConverter();
    mCurrentPhotoList = new ArrayList<String>();
    mIncidentId = new IncidentCardId();
    mIncidentTimestamp = new IncidentCardTimestamp();
    mRes = getResources();
    mPath = new PhotoPath();

    // If savedInstanceState is empty, ignore this code.
    if(savedInstanceState != null){
        mCurrentPhotoPath = savedInstanceState.getString(INSTANCE_STATE);
        mCurrentPhotoList = savedInstanceState.getStringArrayList(INSTANCE_STATE_LIST);
    }
    }

    protected void saveIncidentCard(){
    Realm realm = Realm.getInstance(this);
    Spinner spinner = (Spinner) findViewById(R.id.content_new_card_type);
    String incidentType = spinner.getSelectedItem().toString();

    realm.beginTransaction();
    mIncidentCard.setId(mIncidentId.getNewId());
    mIncidentCard.setTimestamp(mIncidentTimestamp.getNewTimestamp());
    mIncidentCard.setType(incidentType);
    mIncidentCard.setPhotos(mRealmConverter.toRealmList(mCurrentPhotoList));
    realm.commitTransaction();

    mDbHelper.setObject(mIncidentCard);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_new_card, menu);
    return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.menu_new_photo:
            dispatchTakePictureIntent();

        default:
            // If we got here, the user's action was not recognized.
            // Invoke the superclass to handle it.
            return super.onOptionsItemSelected(item);

    }
    }


    private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    File imageFile = null;

    // Ensure that there's a camera activity to handle the intent
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        // Create the File where the photo should go
        try {
            imageFile = mPath.createImageFile();
            mCurrentPhotoPath = imageFile.getAbsolutePath();
        } catch (java.io.IOException e) {
            Log.e(TAG, e.toString());
        }

        // Continue only if the File was successfully created
        if (imageFile != null) {
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                    Uri.fromFile(imageFile.getAbsoluteFile()));
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
    }
    }

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    savedInstanceState.putString(INSTANCE_STATE, mCurrentPhotoPath);
    savedInstanceState.putStringArrayList(INSTANCE_STATE_LIST, mCurrentPhotoList);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        mCurrentPhotoList.add(mCurrentPhotoPath);
    }
    }

    @Override
    protected void onDestroy() {
    super.onDestroy();
    mDbHelper.close();
    }
}

Когда я снимаю новые фотографии, я сохраняю пути к ранее снятым фотографиям в ArrayList типа String. Прежде чем установить список путей к объекту IncidentCard, я конвертирую список в RealmList. Эта часть вроде работает нормально.

Проблема возникает после того, как я пытаюсь сохранить существующий объект в EditCardActivity:

public class EditCardActivity extends AppCompatActivity {

    private static final String INTENT_EXTRA = "EXTRA_INCIDENT_ID";
    private static final int REQUEST_IMAGE_CAPTURE = 2;
    private static final String INSTANCE_STATE = "currentPhotoPath";
    private static final String INSTANCE_STATE_LIST = "currentPhotoList";

    private Context mContext;
    private Long mIncidentId;
    private IncidentCard mIncidentCard;
    private IncidentDbHelper mDbHelper;

    private IncidentCardTimestamp mIncidentTimestamp;
    private RealmConverter mRealmConverter;

    private Resources mRes;
    private PhotoPath mPath;

    Spinner mSpinnerType;
    private String mCurrentPhotoPath;
    private ArrayList<String> mCurrentPhotoList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mRealmConverter = new RealmConverter();

    mContext = this;
    mDbHelper = new IncidentDbHelper(mContext);
    mIncidentCard = new IncidentCard();
    mCurrentPhotoList = new ArrayList<String>();
    mIncidentTimestamp = new IncidentCardTimestamp();
    mRes = getResources();
    mPath = new PhotoPath();

    // If savedInstanceState is empty, ignore this code.
    if(savedInstanceState != null){
        mCurrentPhotoPath = savedInstanceState.getString(INSTANCE_STATE);
        mCurrentPhotoList = savedInstanceState.getStringArrayList(INSTANCE_STATE_LIST);
    }

    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            saveIncidentCard();
            finish();
        }
    });

    // Get Incident ID passed from Main Activity
    Intent intent = getIntent();
    mIncidentId = intent.getLongExtra(INTENT_EXTRA, 0);

    mIncidentCard = mDbHelper.getObject(mIncidentId);

    mTextViewId = (TextView) findViewById(R.id.content_edit_card_id);
    mSpinnerType = (Spinner) findViewById(R.id.content_edit_card_type);

    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
            android.R.layout.simple_spinner_dropdown_item, items);
    mSpinnerType.setAdapter(adapter);
    String compareValue = mIncidentCard.getType();
    if (!compareValue.equals(null)) {
        int spinnerPosition = adapter.getPosition(compareValue);
        mSpinnerType.setSelection(spinnerPosition);
    }

    mCurrentPhotoList = mRealmConverter.toArrayList(mIncidentCard.getPhotos());
    }

    protected void saveIncidentCard(){
    Realm realm = Realm.getInstance(this);
    Spinner spinner = (Spinner) findViewById(R.id.content_edit_card_type);

    String incidentType = spinner.getSelectedItem().toString();

    realm.beginTransaction();
    mIncidentCard.setTimestamp(mIncidentTimestamp.getNewTimestamp());
    mIncidentCard.setType(incidentType);
    mIncidentCard.setPhotos(mRealmConverter.toRealmList(mCurrentPhotoList));
    realm.commitTransaction();

    mDbHelper.setObject(mIncidentCard);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.menu_edit_card, menu);
    return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.menu_edit_photo:
            dispatchTakePictureIntent();

        default:
            // If we got here, the user's action was not recognized.
            // Invoke the superclass to handle it.
            return super.onOptionsItemSelected(item);

    }
    }


    private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    File imageFile = null;

    // Ensure that there's a camera activity to handle the intent
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        // Create the File where the photo should go
        try {
            imageFile = mPath.createImageFile();
            mCurrentPhotoPath = imageFile.getAbsolutePath();
        } catch (java.io.IOException e) {
            Log.e(TAG, e.toString());
        }

        // Continue only if the File was successfully created
        if (imageFile != null) {
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                    Uri.fromFile(imageFile.getAbsoluteFile()));
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
    }
    }

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    savedInstanceState.putString(INSTANCE_STATE, mCurrentPhotoPath);
    savedInstanceState.putStringArrayList(INSTANCE_STATE_LIST, mCurrentPhotoList);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        mCurrentPhotoList.add(mCurrentPhotoPath);
    }
    }

    @Override
    protected void onDestroy() {
    super.onDestroy();
    mDbHelper.close();
    }
}

Итак, если я закомментирую mIncidentCard.setPhotos (), все вроде работает нормально, но когда я пытаюсь установить фотографии в объект IncidentCard, срабатывает исключение IllegalArgumentException.

Это метод, который я создал для преобразования ArrayLists в RealmLists:

public RealmList<IncidentPhoto> toRealmList(ArrayList<String> arrayList){
    mRealmList = new RealmList<IncidentPhoto>();
    for (int i = 0; i < arrayList.size(); i++){
        IncidentPhoto incidentPhoto = new IncidentPhoto();
        incidentPhoto.setPhotoPath(arrayList.get(i));
        mRealmList.add(incidentPhoto);
    }
    return mRealmList;
}

Я уже некоторое время борюсь с этим, и я не понимаю, что делаю не так, поэтому любая помощь будет принята с благодарностью.

«Значение» исключения области не является допустимым управляемым объектом Realm Java Doc


person Oyvind Fredstie    schedule 15.02.2016    source источник
comment
Похоже, вы пытаетесь добавить список строк, когда для этого требуется список объектов IncidentPhoto. Я не думаю, что преобразование Arraylist в Realmlist преобразует строки в объекты IncidentPhoto.   -  person TP89    schedule 15.02.2016
comment
Что ж, я использую тот же метод для преобразования ArrayList в RealmList в NewCardActivity, и там он отлично работает. Пути сохранены, и я могу преобразовать каждую фотографию в растровые изображения в MainActivity. К вашему сведению, я включил метод toRealmList () выше.   -  person Oyvind Fredstie    schedule 15.02.2016


Ответы (1)


Проблема в том, что при вызове сеттеров для установки RealmList каждый элемент в списке уже должен управляться Realm.

Аналогичный вопрос здесь Добавление автономных объектов в RealmList

Вы можете изменить toRealmList, как показано ниже:

public RealmList<IncidentPhoto> toRealmList(Realm realm, ArrayList<String> arrayList) {
    mRealmList = new RealmList<IncidentPhoto>();
    for (int i = 0; i < arrayList.size(); i++){
        // Create a IncidentPhoto object which is managed by Realm.
        IncidentPhoto incidentPhoto = realm.createObject(IncidentPhoto.class);
        incidentPhoto.setPhotoPath(arrayList.get(i));
        mRealmList.add(incidentPhoto);
    }
    return mRealmList;
}

or

public RealmList<IncidentPhoto> toRealmList(Realm realm, ArrayList<String> arrayList) {
    mRealmList = new RealmList<IncidentPhoto>();
    for (int i = 0; i < arrayList.size(); i++){
        IncidentPhoto incidentPhoto = new IncidentPhoto();
        incidentPhoto.setPhotoPath(arrayList.get(i));
        // Copy the standalone object to Realm, and get the returned object which is managed by Realm.
        incidentPhoto = realm.copyToRealm(incidentPhoto);
        mRealmList.add(incidentPhoto);
    }
    return mRealmList;
}
person beeender    schedule 16.02.2016