androidcontrollerandroid-lifecycleconductor

Controller creating duplicate views


I have an app which has a

BaseListActivity which hosts a

BaseController -> Acts as ViewPager

ListController -> Tab 1 of ViewPager

FavoriteController -> Tab 2 of ViewPager

DetailActivity -> Launched when item clicked on Tab 1

DetailController -> Pushed onto view in DetailActivity

ListController DetailController

So code to give you an idea.

activity.xml:

<LinearLayout android:id="@+id/mainLayout"
          xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical">

<com.bluelinelabs.conductor.ChangeHandlerFrameLayout
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

BaseListActivity:

class CoinListActivity : AppCompatActivity() {

private lateinit var router: Router

/**
 * Current implementation uses BlueLineLabs Router to navigate between views and handle animations
 */
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_coin_list)

    router = Conductor.attachRouter(this, container, savedInstanceState)

    router.setRoot(RouterTransaction.with(CoinBaseController()))

    RoomSingleton.initDB(this)
.
.
.

BaseController:

class CoinBaseController : Controller() {


override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
    return inflater.inflate(R.layout.coin_base_controller, container, false).apply {
        viewPager.adapter = TabAdapter()
        tabs.setupWithViewPager(viewPager)
    }
}

.
.
.

inner class TabAdapter : RouterPagerAdapter(this) {
    override fun configureRouter(router: Router, position: Int) {
        router.setPopsLastView(true)
        when (position) {
            0 -> router.pushController(RouterTransaction.with(CoinListController.newInstance()))
            1 -> router.pushController(RouterTransaction.with(CoinFavoriteController()))
        }
        if (router.hasRootController().not())
            router.setRoot(RouterTransaction.with(this@CoinBaseController))
    }

    override fun getCount(): Int = 2

    override fun getPageTitle(position: Int): CharSequence? {
        return if (position == 0)
            "List"
        else
            "Favorites"
    }
}

}

CoinListController:

    class CoinListController : BaseMvvmController<CoinListViewModel, CoinListContract.State>(), CoinListAdapter
.CoinListListener {
var list: List<CoinListItem> = emptyList()
var recyclerView: RecyclerView? = null
lateinit var swipeLayout: SwipeRefreshLayout

private val adapter by lazy(LazyThreadSafetyMode.NONE) {
    CoinListAdapter(applicationContext!!, this)
}


override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
    val view = inflater.inflate(R.layout.coin_list_controller, container, false)
    swipeLayout = view.findViewById(R.id.swipeContainer)
    recyclerView = view.findViewById(R.id.listRecycler)
    recyclerView!!.layoutManager = LinearLayoutManager(activity)
    recyclerView!!.addItemDecoration(DividerItemDecoration(activity,
            DividerItemDecoration.VERTICAL))
    recyclerView!!.adapter = adapter
    recyclerView!!.isNestedScrollingEnabled = false
    view.swipeContainer.apply {
        this.setOnRefreshListener {
            viewModel.getCoinList()
            this.isRefreshing = false
        }
    }
    return view
}


override fun onAttach(view: View) {
    super.onAttach(view)
    with(viewModel) {
        if (list.isEmpty()) {
            getCoinList()
            setSearchListener(view)
        } else {
            view.listRecycler.adapter = adapter
            adapter.notifyDataSetChanged()
        }
    }

}

override fun onDetach(view: View) {
    super.onDetach(view)
    view.listRecycler.adapter = null
    println("Detached")
}


override fun onCoinClicked(coin: CoinListItem) {
    viewModel.onCoinClicked(coin)
}

override fun onFavoriteClicked(coinFavoriteItem: CoinFavoriteItem) {
    viewModel.onFavoriteClicked()
}

override val viewModel: CoinListViewModel = get().koin.get()

override fun onStateChange(state: Mvvm.State) {
    when (state) {
        is CoinListContract.State.CoinListReceived -> {
            list = state.coins
            adapter.addItems(list)
        }
        is CoinListContract.State.CoinItemClicked -> {
            Snackbar.make(view!!, "Coin Item Clicked", Snackbar.LENGTH_SHORT)
            //router.onActivityStarted()
            startActivity(CoinDetailActivity.getLaunchIntent(activity!!, state.coin))
        }
        is CoinListContract.State.FavoriteClicked -> {
            Snackbar.make(view!!, "Favorite Button Clicked", Snackbar.LENGTH_SHORT)
        }
        is CoinListContract.State.QueryRan -> {
            adapter.addItems(state.searchList)
        }
        is CoinListContract.State.Error -> {
            Log.d("OnStateChange", state.throwable.localizedMessage!!)
        }
    }
}

So the problem is in CoinListController, when I click a list item, DetailActivity is launched, then I back out..returning to CoinListController.

However, for each subsequent click of an item, there is an additional listener added(I think). So if i'm on my 3rd click of an item, CoinListController.onCoinClicked is called 3 times, creating 3 instances of CoinDetailActivity. So I then have to press back 3 times in order to return to CoinListController.

This behavior is not present in debug mode..

I'm setting adapters to null on detach. What is persisting through the lifecycle of the controllers? Is it the listener I've set in my adapter or something else?


Solution

  • Setting the root twice in CoinListActivity and CoinBaseController caused the issue