โมดูลสถานะที่บันทึกไว้สำหรับ ViewModel (มุมมอง)   ส่วนหนึ่งของ Android Jetpack

แนวคิดและการใช้งาน Jetpack Compose

ดังที่กล่าวไว้ในการบันทึกสถานะ UI ออบเจ็กต์ ViewModel สามารถจัดการ การเปลี่ยนแปลงการกำหนดค่าได้ คุณจึงไม่ต้องกังวลเกี่ยวกับสถานะในการหมุน หรือกรณีอื่นๆ อย่างไรก็ตาม หากคุณต้องจัดการกระบวนการที่ระบบเริ่มต้น การสิ้นสุด คุณอาจต้องใช้ SavedStateHandle API เป็นข้อมูลสำรอง

โดยปกติแล้ว สถานะ UI จะจัดเก็บหรืออ้างอิงในออบเจ็กต์ ViewModel และไม่ใช่ กิจกรรม ดังนั้นการใช้ onSaveInstanceState() จึงต้องมีบอยเลอร์เพลตบางอย่างที่โมดูลสถานะที่บันทึกไว้จะจัดการให้คุณได้

เมื่อใช้โมดูลนี้ ออบเจ็กต์ ViewModel จะได้รับออบเจ็กต์ SavedStateHandle ผ่านตัวสร้าง ออบเจ็กต์นี้คือแผนที่คีย์-ค่าที่ช่วยให้คุณ เขียนและเรียกออบเจ็กต์ไปยังและจากสถานะที่บันทึกไว้ได้ ค่าเหล่านี้จะยังคงอยู่ หลังจากที่ระบบปิดกระบวนการและยังคงใช้ได้ผ่านออบเจ็กต์เดียวกัน

สถานะที่บันทึกไว้จะเชื่อมโยงกับสแต็กงาน หากสแต็กงานหายไป สถานะที่บันทึกไว้ก็จะหายไปด้วย ซึ่งอาจเกิดขึ้นเมื่อบังคับหยุดแอป นำแอปออกจากเมนู "ล่าสุด" หรือรีบูตอุปกรณ์ ในกรณีเช่นนี้ งาน สแต็กจะหายไปและคุณจะกู้คืนข้อมูลในสถานะที่บันทึกไว้ไม่ได้ ในสถานการณ์การปิดสถานะ UI ที่ผู้ใช้เริ่ม ระบบจะไม่คืนค่าสถานะที่บันทึกไว้ ในสถานการณ์ที่ระบบเริ่มต้น

ตั้งค่า

ตั้งแต่ Fragment 1.2.0 หรือทรัพยากร Dependency แบบทรานซิทีฟ Activity 1.1.0 เป็นต้นไป คุณจะยอมรับ SavedStateHandle เป็นอาร์กิวเมนต์ของเครื่องมือสร้าง สำหรับ ViewModel ได้

Kotlin

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle state;

    public SavedStateViewModel(SavedStateHandle savedStateHandle) {
        state = savedStateHandle;
    }

    ...
}

จากนั้นคุณจะเรียกข้อมูลอินสแตนซ์ของ ViewModel ได้โดยไม่ต้องกำหนดค่าเพิ่มเติม ViewModelเริ่มต้นจากโรงงานจะให้SavedStateHandleที่เหมาะสมแก่ViewModel

Kotlin

class MainFragment : Fragment() {
    val vm: SavedStateViewModel by viewModels()

    ...
}

Java

class MainFragment extends Fragment {
    private SavedStateViewModel vm;

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        vm = new ViewModelProvider(this).get(SavedStateViewModel.class);

        ...

    }

    ...
}

เมื่อระบุอินสแตนซ์ ViewModelProvider.Factory ที่กำหนดเอง คุณจะ เปิดใช้ SavedStateHandle ได้โดยการขยาย AbstractSavedStateViewModelFactory

การทำงานกับ SavedStateHandle

SavedStateHandle class คือแผนที่คีย์-ค่าที่ช่วยให้คุณเขียนและ เรียกข้อมูลไปยังและจากสถานะที่บันทึกไว้ผ่านเมธอด set() และ get()

การใช้ SavedStateHandle จะช่วยให้ค่าการค้นหายังคงอยู่เมื่อการประมวลผลสิ้นสุดลง เพื่อให้มั่นใจว่าผู้ใช้จะเห็นชุดข้อมูลที่กรองแล้วชุดเดียวกันทั้งก่อนและหลัง การสร้างใหม่โดยไม่ต้องให้กิจกรรมหรือ Fragment บันทึก กู้คืน และส่งต่อค่านั้นกลับไปยัง ViewModel ด้วยตนเอง

SavedStateHandle ยังมีเมธอดอื่นๆ ที่คุณอาจคาดหวังเมื่อโต้ตอบ กับแมปคีย์-ค่าด้วย

  • contains(String key) - ตรวจสอบว่ามีค่าสำหรับคีย์ที่ระบุหรือไม่
  • remove(String key) - นำค่าสำหรับคีย์ที่ระบุออก
  • keys() - แสดงคีย์ทั้งหมดที่อยู่ใน SavedStateHandle

นอกจากนี้ คุณยังดึงค่าจาก SavedStateHandle ได้โดยใช้ที่เก็บข้อมูลที่ได้รับอนุญาตให้สังเกตพฤติกรรมผู้ใช้ได้ รายการประเภทที่รองรับมีดังนี้

LiveData

ดึงค่าจาก SavedStateHandle ที่อยู่ใน LiveData observable โดยใช้ getLiveData() เมื่อมีการอัปเดตค่าของคีย์ LiveData จะได้รับค่าใหม่ โดยส่วนใหญ่แล้ว ค่าจะตั้งขึ้นเนื่องจากการโต้ตอบของผู้ใช้ เช่น การป้อนคำค้นหาเพื่อกรองรายการข้อมูล จากนั้นจะใช้ค่าที่อัปเดตนี้เพื่อเปลี่ยนรูปแบบ LiveData ได้

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    val filteredData: LiveData<List<String>> =
        savedStateHandle.getLiveData<String>("query").switchMap { query ->
        repository.getFilteredData(query)
    }

    fun setQuery(query: String) {
        savedStateHandle["query"] = query
    }
}

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle savedStateHandle;
    public LiveData<List<String>> filteredData;
    public SavedStateViewModel(SavedStateHandle savedStateHandle) {
        this.savedStateHandle = savedStateHandle;
        LiveData<String> queryLiveData = savedStateHandle.getLiveData("query");
        filteredData = Transformations.switchMap(queryLiveData, query -> {
            return repository.getFilteredData(query);
        });
    }

    public void setQuery(String query) {
        savedStateHandle.set("query", query);
    }
}

ประเภทที่รองรับ

ระบบจะบันทึกและกู้คืนข้อมูลที่เก็บไว้ภายใน SavedStateHandle เป็น Bundle พร้อมกับsavedInstanceStateที่เหลือสำหรับกิจกรรมหรือ ส่วน

การบันทึกคลาสที่ส่งผ่านไม่ได้

หากคลาสไม่ได้ใช้ Parcelable หรือ Serializable และแก้ไขไม่ได้เพื่อใช้หนึ่งในอินเทอร์เฟซเหล่านั้น ก็จะบันทึกอินสแตนซ์ของคลาสดังกล่าวลงใน SavedStateHandle โดยตรงไม่ได้

ตั้งแต่ Lifecycle 2.3.0-alpha03 เป็นต้นไป SavedStateHandle จะช่วยให้คุณบันทึก ออบเจ็กต์ใดก็ได้โดยการระบุตรรกะของคุณเองสำหรับการบันทึกและกู้คืนออบเจ็กต์เป็น Bundle โดยใช้วิธี setSavedStateProvider() SavedStateRegistry.SavedStateProvider คืออินเทอร์เฟซที่กำหนดเมธอด saveState() เดียวที่ส่งคืน Bundle ซึ่งมีสถานะที่คุณต้องการบันทึก เมื่อ SavedStateHandle พร้อมที่จะบันทึกสถานะของตัวเองแล้ว ก็จะเรียกใช้ saveState() เพื่อดึงข้อมูล Bundle จาก SavedStateProvider และบันทึก Bundle สำหรับคีย์ที่เชื่อมโยง

ลองพิจารณาตัวอย่างแอปที่ขอรูปภาพจากแอปกล้องถ่ายรูปผ่าน Intent ACTION_IMAGE_CAPTURE โดยส่งไฟล์ชั่วคราวสำหรับตำแหน่งที่ กล้องควรจัดเก็บรูปภาพ TempFileViewModel จะห่อหุ้ม ตรรกะสำหรับการสร้างไฟล์ชั่วคราวดังกล่าว

Kotlin

class TempFileViewModel : ViewModel() {
    private var tempFile: File? = null

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel() {
    }

    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }
}

TempFileViewModel สามารถใช้ SavedStateHandle เพื่อคงข้อมูลไว้ได้ เพื่อให้มั่นใจว่าไฟล์ชั่วคราวจะไม่สูญหายหากกระบวนการของกิจกรรมถูกปิด และกู้คืนในภายหลัง หากต้องการอนุญาตให้ TempFileViewModel บันทึกข้อมูล ให้ใช้ SavedStateProvider และตั้งค่าเป็นผู้ให้บริการใน SavedStateHandle ของ ViewModel ดังนี้

Kotlin

private fun File.saveTempFile() = bundleOf("path", absolutePath)

class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    private var tempFile: File? = null
    init {
        savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
            if (tempFile != null) {
                tempFile.saveTempFile()
            } else {
                Bundle()
            }
        }
    }

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel(SavedStateHandle savedStateHandle) {
        savedStateHandle.setSavedStateProvider("temp_file",
            new TempFileSavedStateProvider());
    }
    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }

    private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
        @NonNull
        @Override
        public Bundle saveState() {
            Bundle bundle = new Bundle();
            if (tempFile != null) {
                bundle.putString("path", tempFile.getAbsolutePath());
            }
            return bundle;
        }
    }
}

หากต้องการกู้คืนข้อมูล File เมื่อผู้ใช้กลับมา ให้ดึงข้อมูล temp_file Bundle จาก SavedStateHandle ซึ่งเป็น Bundle เดียวกับที่ saveTempFile() ระบุไว้ซึ่งมีเส้นทางที่แน่นอน จากนั้นจะใช้เส้นทางแบบสัมบูรณ์เพื่อสร้างอินสแตนซ์ File ใหม่ได้

Kotlin

private fun File.saveTempFile() = bundleOf("path", absolutePath)

private fun Bundle.restoreTempFile() = if (containsKey("path")) {
    File(getString("path"))
} else {
    null
}

class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    private var tempFile: File? = null
    init {
        val tempFileBundle = savedStateHandle.get<Bundle>("temp_file")
        if (tempFileBundle != null) {
            tempFile = tempFileBundle.restoreTempFile()
        }
        savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
            if (tempFile != null) {
                tempFile.saveTempFile()
            } else {
                Bundle()
            }
        }
    }

    fun createOrGetTempFile(): File {
      return tempFile ?: File.createTempFile("temp", null).also {
          tempFile = it
      }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel(SavedStateHandle savedStateHandle) {
        Bundle tempFileBundle = savedStateHandle.get("temp_file");
        if (tempFileBundle != null) {
            tempFile = TempFileSavedStateProvider.restoreTempFile(tempFileBundle);
        }
        savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider());
    }

    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }

    private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
        @NonNull
        @Override
        public Bundle saveState() {
            Bundle bundle = new Bundle();
            if (tempFile != null) {
                bundle.putString("path", tempFile.getAbsolutePath());
            }
            return bundle;
        }

        @Nullable
        private static File restoreTempFile(Bundle bundle) {
            if (bundle.containsKey("path") {
                return File(bundle.getString("path"));
            }
            return null;
        }
    }
}