Android Realm - 更新 RealmList 触发 IllegalArgumentException

Android Realm - Updating RealmList triggers IllegalArgumentException

我正在尝试更新现有的 RealmObject (IncidentCard),其中包含类型为 IncidentPhoto 的 RealmList。只要我不尝试更新 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.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)

这是事件卡片class:

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;
    }
}

这是事故照片class:

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;
    }
}

为了查询领域数据库,我创建了这个助手 class:

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();
    }
    }
}

当我添加新的事件卡时,我将其称为 activity:

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 中。在我将路径列表设置为 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

问题是,当调用 setter 来设置一个 RealmList 时,列表中的每个元素都必须由 Realm 管理。

这里有类似的问题

您可以修改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;
}

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;
}