I’m new to android development using KOTLIN, i have activity that contain fragments like the image below (Image 1), the problem is whenever i try to change the orientation from potrait to landscape, the activity return to the previous activity (Like restart it).
I’ve tried to add android:configChanges="orientation|screenSize|keyboardHidden"
to my AndroidManifest.xml
, it works perfectly but some says that it’s not recommended to use it.
Could you guys tell me or show me example the best practice to solve this problem ?
IMAGE 1
CODE
MainActivity.kt
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) title = resources.getString(R.string.title_visit) loadFragment(VisitFragment()) navigation_menu.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener) } private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item -> when (item.itemId) { R.id.menu_visit -> { loadFragment(VisitFragment()) title = resources.getString(R.string.title_visit) return@OnNavigationItemSelectedListener true } R.id.menu_customer -> { loadFragment(CustomerFragment()) title = resources.getString(R.string.title_customer) return@OnNavigationItemSelectedListener true } R.id.menu_new_merchant -> { loadFragment(NewMerchantFragment()) title = resources.getString(R.string.title_new_merchant) return@OnNavigationItemSelectedListener true } R.id.menu_history -> { loadFragment(HistoryFragment()) title = resources.getString(R.string.title_history) return@OnNavigationItemSelectedListener true } R.id.menu_profile -> { loadFragment(ProfileFragment()) title = resources.getString(R.string.title_profile) return@OnNavigationItemSelectedListener true } } false } private fun loadFragment(fragment: Fragment) { val transaction = supportFragmentManager.beginTransaction() transaction.replace(R.id.container, fragment) transaction.addToBackStack(null) transaction.commit() } }
VisitFragment.kt
class VisitFragment : Fragment() { private lateinit var viewPager: ViewPager private lateinit var tabs: TabLayout override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_visit, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val fragmentAdapter = PagerAdapter(childFragmentManager) pager.adapter = fragmentAdapter tabs_main.setupWithViewPager(pager) } fun setNumber() { val tabs = tabs_main.getTabAt(0) val badge = tabs?.orCreateBadge // Customize badge badge?.number = 1 } }
LatestVisitFragment.kt
class LatestVisitFragment : Fragment() { lateinit var latestVisitAdapter: LatestVisitAdapter lateinit var recyclerView: RecyclerView private val testinstance: ArrayList<TestResponseItem> = ArrayList() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_latest_visit, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) loadDataLatestVisit() } private fun loadDataLatestVisit(){ ApiConfig().getService() .getUsers() .enqueue(object : Callback<List<TestResponseItem>> { override fun onFailure(call: Call<List<TestResponseItem>>, t: Throwable) { Toast.makeText(context, t.localizedMessage, Toast.LENGTH_SHORT).show() } override fun onResponse( call: Call<List<TestResponseItem>>, response: Response<List<TestResponseItem>> ) { rv_latest_visit.adapter = LatestVisitAdapter(response.body()) } }) } }
LatestVisitAdapter.kt
class LatestVisitAdapter(val data: List<TestResponseItem>?) : RecyclerView.Adapter<LatestVisitAdapter.MyHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder { val v = LayoutInflater.from(parent.context).inflate(R.layout.item_latest_visit, parent, false) return MyHolder(v) } override fun getItemCount(): Int = data?.size ?: 0 override fun onBindViewHolder(holder: LatestVisitAdapter.MyHolder, position: Int) { holder.bind(data?.get(position)) } class MyHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(get: TestResponseItem?) { itemView.txt_merchant_name.text = get?.name itemView.txt_owner_name.text = get?.email val address = "${get?.address?.street},${get?.address?.city}, ${get?.address?.suite}, ${get?.address?.zipcode}" itemView.txt_address.text = address } } }
Advertisement
Answer
It really is not recommended to prevent activity restart on config changes.
The recommended ways would either by saving and restoring UI state or by using ViewModel. Either one of them can solve your problem but it’s better to use ViewModel approach.
Saving and restoring UI state
Saving state
Right before activity start, but right after config changes have been signaled, override Activity.onSaveInstanceState(Bundle)
if you’re saving activity state, or `Fragment.onSaveInstanceState(Bundle) if you’re saving fragment state.
override fun onSaveInstanceState(outState: Bundle?) { // Save your data into outState data bundle. And then make sure to // call the super method. super.onSaveInstanceState(outState) }
Restoring state
After activity restart due to config changes, restore the previously saved data and apply it to the UI.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... inflate layout, etc... if (savedInstanceState != null) { // If savedInstanceState is not null, that means this activity is restoring from // config changes. All your saved data in onSaveInstanceState() should be accessible // from savedInstanceState bundle. // ... restore values from savedInstanceState and apply to your views ... } else { // Initialize vie } }
Using ViewModel
This approach is relatively new introduced by Google as part of Android Jetpack library.
Instead of overriding onSaveInstanceState(Bundle) and checking
savedInstanceStatefor null, your data is persisted inside a
ViewModel` and will survive from configuration changes.
Basic
Initialize your data inside the ViewModel
and access them from your activity or fragment.
class MyViewModel : ViewModel() { var myList: List<User> = emptyList() var currentTabIndex: Int = 0 init { // Initialize your data here... } }
class MyFragment : Fragment() { private val model by viewModels<MyViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... inflate layout, etc... viewPager.setCurrentItem(model.currentTabIndex, false) // Fetch the values from `ViewModel` and apply to your fragment. } }
More about ViewModel
For better usage of ViewModel
, it’s better to learn from the official comprehensive guide.