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)
CodePudding user response:
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()
}
}