Search code examples
androidandroid-activitykotlininterfaceandroid-lifecycle

Communication between two activities using interface not working as expected


PROBLEM: On sending data from activity2 to activity1 using interface results in non-assignment of send data in activity1 even if it is being assigned. Consequently, activity1 members variables are automatically set to null.

SETUP DETAIL: On clicking a button in activity1, launches activity2. Now a user can perform whatever work in activity2. But as soon as a user performs work in activity2, my custom interface sends a data to activity1. Activity1 is supposed to assign that sent value to its member variable and when the user navigates back to activity1, onRestart() method of activity1 is bound to update user-interface with a new value.

SETUP IN POINTS:

1- User clicks a button on ACTIVITY1

2- Button opens ACTIVITY2

3- User performs an action that triggers my interface to send a data

4- Interface sends data to ACTIVITY1 in the background(ACTIVITY2 is still visible)

5- Sent data is assigned to ACTIVITY1 member variable(boolean) via interface

6- User back presses on ACTIVITY2 and navigates back to ACTIVITY1

7- ONRESTART() of ACTIVITY1 gets triggered

8- ONRESTART() checks if the boolean is true, then update UI

But here my confusion starts. Why is the assigned boolean value still null even after being assigned? Why is ACTIVITY1`s member variables losing assigned values?

CODE:

Interface

override fun onReturnedToActivity(actionID: Int) {
    when(actionID)
    {
        3 ->
        {
            newNoteCreated = true
        }
    }
}

ONRESTART()

override fun onRestart() {
    super.onRestart()

    if(newNoteCreated)  //variable is null not true.
    {
        Toast.makeText(this,"TRUE",Toast.LENGTH_SHORT).show()
        mDrawer!!.updateBadge(3, StringHolder(dbHandler.totalNotes().toString() ))

        newNoteCreated = false
    }

Please educate me on this problem. I have been trying to find its solution on SO but nothing yet answers my question. I know there are other ways to send data between activities but I am trying to particularly use interfaces.

Quoting from

https://developer.android.com/reference/android/app/Activity#ActivityLifecycle

"If an activity is completely obscured by another activity, it is stopped. It still retains all state and member information, however, it is no longer visible to the user so its window is hidden and it will often be killed by the system when memory is needed elsewhere."

Then why does my activity loses its variable data?

EDIT

Main Activity(ACTIVITY1)

class MainActivity : AppCompatActivity(), ContextBarVisibilityListener, OnActionPerformedBadgeUpdater, ActivityReturningStateNotify {


private val dbHandler: PediaDatabase = PediaDatabase(this)
private var mDrawer: Drawer? = null
private var filePath: String? = null
private var backupFileName: String? = null
private var listBt: MenuItem? = null
private var gridBt: MenuItem? = null
private var frontAppName: TextView? = null
private var buttonStatePref: SharedPreferences? = null
private var speedDialView: SpeedDialView? = null
private var trashNotesCount: String? = null
private var archiveNotesCount: String? = null
private var onLayoutChange: LayoutChangeListener? = null

private var newNoteCreated: Boolean = false //THIS IS THE BOOL

private lateinit var mAdView: AdView



companion object
{
    private const val LAYOUT_USER_PREF = "layout_preference"
}


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)


    onLayoutChange = NotesFrag()

    if(savedInstanceState == null) //prevents reloading fragment
    {
        val transaction = supportFragmentManager.beginTransaction()
        transaction.add(R.id.innerFrameLayout,NotesFrag()).commit()
    }



    trashNotesCount = dbHandler.trashedNotesCount().toString()
    archiveNotesCount = dbHandler.archivedNoteCount().toString()

    val drawerTrashBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(1).withName("Trash Bin")
            .withIcon(R.drawable.delete_note_image)
            .withBadge(trashNotesCount)
            .withSelectedIcon(R.drawable.delete_drawer_selected)

    val drawerArchiveBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(2).withName("Archive")
            .withIcon(R.drawable.archive_note_img)
            .withBadge(archiveNotesCount)
            .withSelectedIcon(R.drawable.archive_drawer_selected)

    val drawerNotesBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(3).withName("Notes")
            .withIcon(R.drawable.notes_img)
            .withBadge(dbHandler.totalNotes().toString())
            .withSelectedIcon(R.drawable.notes_drawer_selected)

    val drawerLockedNotesBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(4).withName("Locked Notes")
            .withIcon(R.drawable.lock_button)
            .withBadge("1")
            .withSelectedIcon(R.drawable.locked_drawer_selected)

    val drawerReminderNotesBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(5).withName("Reminder Notes")
            .withIcon(R.drawable.reminder_notes)
            .withBadge("1")
            .withSelectedIcon(R.drawable.reminder_drawer_selected)

    val drawerFavouriteNotesBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(6).withName("Favourite Notes")
            .withIcon(R.drawable.rate_app_star_img)
            .withBadge("1")
            .withSelectedIcon(R.drawable.favourite_drawer_selected)

    val drawerSettingsBt: PrimaryDrawerItem = PrimaryDrawerItem().withIdentifier(7).withName("Settings")
            .withIcon(R.drawable.settings_drawer)



      mDrawer = DrawerBuilder().withActivity(this)
            .withHeader(R.layout.navbar_header)
            .addDrawerItems(drawerNotesBt,drawerArchiveBt,drawerTrashBt,
                    drawerLockedNotesBt,drawerReminderNotesBt,
                    drawerFavouriteNotesBt,DividerDrawerItem(), drawerSettingsBt)

            .withOnDrawerItemClickListener(object: Drawer.OnDrawerItemClickListener{
                override fun onItemClick(view: View?, position: Int, drawerItem: IDrawerItem<*, *>?): Boolean {

                    when(position)
                    {
                        1 ->
                        {
                            frontAppName!!.text = "Wonder Notes"
                            speedDialView!!.show() //show again because traversing to other fragments hides it

                            val transaction = supportFragmentManager.beginTransaction()
                            transaction.replace(R.id.innerFrameLayout, NotesFrag())
                            transaction.addToBackStack(null)
                            transaction.commit()

                        }

                        2 ->
                        {
                            speedDialView!!.hide()
                            supportFragmentManager.beginTransaction().replace(R.id.innerFrameLayout,ArchiveFragment()).commit()
                        }

                        3 ->
                        {
                            frontAppName!!.text = "Trash"
                            speedDialView!!.hide()


                            fabLayoutBehaviourSetter(false)

                            supportFragmentManager.beginTransaction().replace(R.id.innerFrameLayout, TrashFragment()).commit()
                        }
                        4 ->  Log.d("locked notes","1")

                        5 ->  Log.d("reminder","1")

                        6 ->  Log.d("favourite","1")
                    }

                    return false // false closes drawer on click
                }
            }).build()


    /*Below code handles swipe tabs adapter setting and displaying
     * and implements fragments to viewpager programitically */

  //  mainActivityViewPagerID.adapter = ViewPagerAdapter(supportFragmentManager)

   // attachNotesFrag()




    //mainTabsID.setupWithViewPager(mainActivityViewPagerID)
    //mainTabsID.setTabTextColors(Color.DKGRAY, Color.parseColor("white"))




    val drawerOpener = findViewById<ImageButton>(R.id.drawerOpenImgBt)
    val noteCounterAsBt = findViewById<TextView>(R.id.showNotesCountID)




    drawerOpener.setOnClickListener { mDrawer!!.openDrawer() }
    noteCounterAsBt.setOnClickListener { mDrawer!!.openDrawer() }
    //navigationView.setNavigationItemSelectedListener(this)



    val customToolbar: Toolbar = findViewById(R.id.toolbarID)
      setSupportActionBar(customToolbar)


      supportActionBar!!.setDisplayShowTitleEnabled(false) //set main title off

       val toolbarTxtView = findViewById<TextView>(R.id.toolbar_title)
         toolbarTxtView.visibility = View.GONE


    checkForStoragePermission()
}//on create




override fun onContextBarVisibilityChange(isVisible: Boolean) { //custom interface

    if(isVisible)
    {
        speedDialView!!.hide()
        fabLayoutBehaviourSetter(false) //remove scrolling behaviour

    }
    else
    {
        speedDialView!!.show()
        fabLayoutBehaviourSetter(true) //set scrolling behaviour
    }

}


override fun onReturnedToActivity(actionID: Int) { ///it just recieves value from ACTIVITY2 and assign its value to ACTIVTY1`s "newNoteCreated" varaible
    when(actionID)
    {
        3 ->
        {
            Log.d("INTERFACE","CALLED $actionID")
            newNoteCreated = true

            Log.d("INTERFACE","CALLED $newNoteCreated")
        }
    }
}

REFERENCE ACTIVITY(ACTIVITY2) I am only including the part where interface sends data back to activity1 because the code of activity2 is very lengthy and will most likely confuse readers.

 onActivityReturningStateNotify = MainActivity()


 dbHandler!!.createNote(note)

                //onActionPerformedBadgeUpdater!!.onActionPerformed(3)
                onActivityReturningStateNotify!!.onReturnedToActivity(3)

INTERFACE

interface ActivityReturningStateNotify {

fun onReturnedToActivity(actionID: Int)

}


Solution

  • Your problem is that you are calling onReturnedToActivity() on a new instance of MainActivity, not the one you started from or the one that you will return to.

    There are several ways to share data between activities, one is to use startActivityForResult().

    I think your problem however is that Activity1 will be stopped, so you can't really send it data from Activity2. You can however start Activity2 with startActivityForResult and return a result. Here is the example from the documentation.

    Starting the activity.

    const val PICK_CONTACT_REQUEST = 1  // The request code
    ...
    private fun pickContact() {
        Intent(Intent.ACTION_PICK, Uri.parse("content://contacts")).also { pickContactIntent ->
            pickContactIntent.type = Phone.CONTENT_TYPE // Show user only contacts w/ phone numbers
            startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST)
        }
    }
    

    Getting the returned data.

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        // Check which request we're responding to
        if (requestCode == PICK_CONTACT_REQUEST) {
            // Make sure the request was successful
            if (resultCode == Activity.RESULT_OK) {
                // The user picked a contact.
                // The Intent's data Uri identifies which contact was selected.
    
                // Do something with the contact here (bigger example below)
            }
        }
    }
    

    Other schemes include a Global Singleton, launching activities with Intents, or using SharedPreferences writting data in one activity and then reading it back in another. If you do a search on android share data between activities You will see there are various options on the best way to accomplish this.