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
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?
Setting the root twice in CoinListActivity
and CoinBaseController
caused the issue