[FIXED] Can not perform this action after onSaveInstanceState when closing an activity

Issue

I know this error is very common, but I couldn’t find a solution to my problem in the way too many places this error is brought up.

I’m developing an app in order to store and sort TV shows. I’ve got a main activity with some fragments, with a HomeFragment which is the home page, with an ‘Add show’ button, and below a recyclerView with all my shows.

When clicking on the ‘Add show’ button, I start a new activity in order to fill a form and then create the show with the provided informations. No problem here, that works as it should. Now I’m trying to add the possibility to edit the shows by clicking on them in the recyclerView I talked about above. This also brings up the same activity as the ‘Add show’ button, but this time with the show informations.

And this is from this page that the problem seems to be coming. In the form activity, I have an button in which I pick an image for the show. When editing the show, if I change the image, I get no error, but if I change something else, for example the name, without changing this image, when clicking the confirm button, the show is correctly edited but the app crashes with the java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState error.

The error seems to be coming from the fragment part, with the transaction being unable to commit (I’ve searched for a while so I began to understand why that wasn’t working, but couldn’t determine which part of the code makes it this way). Here is the fragment:

class HomeFragment(private val context: MainActivity) : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.fragment_home, container, false)

        view.findViewById<Button>(R.id.button_add_show).setOnClickListener{
            startActivity(Intent(context, AddShowActivity::class.java))
        }
        
        val verticalRecyclerView = view.findViewById<RecyclerView>(R.id.vertical_recycler_view)
        verticalRecyclerView.adapter = ShowAdapter(context, showList, R.layout.item_show)

        return view
    }
}

And here the MainActivity part where it’s loaded:

private fun loadFragment(fragment: Fragment){
   // Load repository
   val repo = ShowRepository()

    // Update shows list
    repo.updateData{
        // Inject fragment into fragment_container
        val transaction = supportFragmentManager.beginTransaction()
        transaction.replace(R.id.fragment_container, fragment)
        transaction.addToBackStack(null)
        transaction.commit()
    }
}

Here is the code of my AddShowActivity, which renders the form to fill:

class AddShowActivity : AppCompatActivity() {
    private var fileImage: Uri? = null
    private lateinit var uploadedImage: ImageView
    private lateinit var editTextName: EditText
    private lateinit var editTextNote: EditText
    private lateinit var editTextDescription: EditText
    private lateinit var editTextReview: EditText
    private lateinit var datePicker: DatePicker
    private var currentShow: ShowModel? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_add_show)
        setupComponents()
        setupButtons()
        
        // Get show when editing
        if(intent.extras != null){
            val position = intent.extras!!.getInt("position")
            currentShow = showList[position]
        }

        initializeComponents()

    }

    private fun setupComponents() {
        editTextName = findViewById(R.id.name_input)
        editTextNote = findViewById(R.id.note_input)
        editTextDescription = findViewById(R.id.description_input)
        editTextReview = findViewById(R.id.review_input)
        uploadedImage = findViewById(R.id.preview_image)
        datePicker = findViewById(R.id.watch_date_input)
    }

    private fun setupButtons(){
        val pickupImageButton = findViewById<Button>(R.id.upload_image_button)
        pickupImageButton.setOnClickListener{
            pickupImage()
        }

        val confirmButton = findViewById<Button>(R.id.confirm_button)
        confirmButton.setOnClickListener{
            sendForm()
            val toastText = when(currentShow){
                null -> "Show added"
                else -> "Show edited"
            }
            Toast.makeText(this, toastText, Toast.LENGTH_SHORT).show()
            this.finish()
        }
    }

    @SuppressLint("NewApi")
    private fun initializeComponents() {
        if(currentShow != null){
            editTextName.setText(currentShow!!.name)
            editTextNote.setText(currentShow!!.note.toString())
            editTextDescription.setText(currentShow!!.description)
            editTextReview.setText(currentShow!!.review)
            Glide.with(this).load(Uri.parse(currentShow!!.imageUrl)).into(uploadedImage)
        }
    }

    private fun sendForm(){
        val repo = ShowRepository()
        if(fileImage == null)createShow(repo)
        else{
            if(currentShow != null)repo.deleteImage(currentShow!!)
            repo.uploadImage(fileImage!!){
                createShow(repo)
            }
        }
    }

    private fun createShow(repo: ShowRepository){
        val showName = editTextName.text.toString()
        val showNote = parseInt(editTextNote.text.toString())
        val description = editTextDescription.text.toString()
        val review = editTextReview.text.toString()
        val showWatchDate = getWatchDate(datePicker)
        val downloadImageUrl = downloadImageUri.toString()

        val show = ShowModel(UUID.randomUUID().toString(), showName, showWatchDate, showNote, downloadImageUrl, description, review)
        if(currentShow != null){
            show.id = currentShow!!.id
            repo.updateShow(show)
        }
        else repo.insertShow(show)
    }

    private fun getWatchDate(datePicker: DatePicker): String {
        var day = datePicker.dayOfMonth.toString()
        if(day.toInt() < 10)day = "0$day"
        var month = (datePicker.month + 1).toString()
        if(month.toInt() < 10)month = "0$month"
        val year = datePicker.year.toString()
        return "$day-$month-$year"
    }

    private fun pickupImage(){
        val intent = Intent()
        intent.type = "image/"
        intent.action = Intent.ACTION_GET_CONTENT
        startActivityForResult(Intent.createChooser(intent, "Select Picture"), 47)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if(resultCode == Activity.RESULT_OK && requestCode == 47){
            if(data == null || data.data == null)return
            fileImage = data.data
            uploadedImage.setImageURI(fileImage)
        }
    }
}

Here the ShowRepository, which handles the communication with the Firebase database:

class ShowRepository {

    object Singleton{
        // Link to bucket
        private val BUCKET_URL: String = "gs://tv-memories.appspot.com"

        // Storage connexion
        val storageReference = FirebaseStorage.getInstance().getReferenceFromUrl(BUCKET_URL)

        // Database connexion
        val databaseRef = FirebaseDatabase.getInstance().getReference("shows")

        // List containing all shows
        val showList = arrayListOf<ShowModel>()

        // Contains current image link
        var downloadImageUri: Uri? = null
    }

    fun updateData(callback: () -> Unit){
        // Absorb data from databaseRef
        databaseRef.addValueEventListener(object : ValueEventListener {
            override fun onDataChange(snapshot: DataSnapshot) {
                // Remove old shows
                showList.clear()

                // Get list
                for(ds in snapshot.children){
                    //Build show object
                    val show = ds.getValue(ShowModel::class.java)

                    // Verify show isn't null
                    if(show != null){
                        // Add show to the list
                        showList.add(show)
                    }
                }
                // Activate callback
                callback()
            }

            override fun onCancelled(p0: DatabaseError) {

            }

        })
    }

    // Upload files on storage
    fun uploadImage(file: Uri, callback: () -> Unit){
        val fileName = UUID.randomUUID().toString() + ".jpg"
        val ref = storageReference.child(fileName)
        val uploadTask = ref.putFile(file)

        uploadTask.continueWithTask(Continuation<UploadTask.TaskSnapshot, Task<Uri>>{ task ->
            if(!task.isSuccessful){
                task.exception?.let{throw it}
            }

            return@Continuation ref.downloadUrl
        }).addOnCompleteListener{ task ->
            if(task.isSuccessful){
                downloadImageUri = task.result
                callback()
            }
        }
    }

    fun deleteImage(show: ShowModel){
        val photoRef: StorageReference = FirebaseStorage.getInstance().getReferenceFromUrl(show.imageUrl)
        photoRef.delete()
    }

    fun updateShow(show: ShowModel) = databaseRef.child(show.id).setValue(show)

    fun insertShow(show: ShowModel) = databaseRef.child(show.id).setValue(show)

    fun deleteShow(show: ShowModel){
        databaseRef.child(show.id).removeValue()
        deleteImage(show)
    }
}

And the full traceback of the error:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: fr.steph.showmemories, PID: 18296
    java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
        at androidx.fragment.app.FragmentManager.checkStateLoss(FragmentManager.java:1844)
        at androidx.fragment.app.FragmentManager.enqueueAction(FragmentManager.java:1884)
        at androidx.fragment.app.BackStackRecord.commitInternal(BackStackRecord.java:329)
        at androidx.fragment.app.BackStackRecord.commit(BackStackRecord.java:294)
        at fr.steph.showmemories.MainActivity$loadFragment$1.invoke(MainActivity.kt:49)
        at fr.steph.showmemories.MainActivity$loadFragment$1.invoke(MainActivity.kt:44)
        at fr.steph.showmemories.ShowRepository$updateData$1.onDataChange(ShowRepository.kt:61)
        at com.google.firebase.database.core.ValueEventRegistration.fireEvent(ValueEventRegistration.java:75)
        at com.google.firebase.database.core.view.DataEvent.fire(DataEvent.java:63)
        at com.google.firebase.database.core.view.EventRaiser$1.run(EventRaiser.java:55)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7078)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:964)

Solution

I finally found a solution to my problem, which is to replace transaction.commit() by transaction.commitAllowingStateLoss() in my loadFragment() method in case the state has been saved.

I then get

private fun loadFragment(fragment: Fragment){
        // Load repository
        val repo = ShowRepository()

        // Update shows list
        repo.updateData{
            // Inject fragment into fragment_container
            val transaction = supportFragmentManager.beginTransaction()
            transaction.replace(R.id.fragment_container, fragment)
            transaction.addToBackStack(null)
            if(supportFragmentManager.isStateSaved)transaction.commitAllowingStateLoss()
            else transaction.commit()
        }
    }

Answered By – Balizok

Answer Checked By – David Goodson (Easybugfix Volunteer)

Leave a Reply

(*) Required, Your email will not be published