Search code examples
androidkotlinandroid-activityandroid-actionbarandroid-menu

How to use the new ComponentActivity with ViewBinding and the other old AppCompatActivity components


According to this question, I tried to update my deprecated menus codes like setHasOptionsMenu , onCreateOptionsMenu and onOptionsItemSelected in my fragments and all app, but I should replace AppCompatActivity to ComponentActivity(R.layout.activity_example) but after doing this I see there's some problem, first I confused about how to use ViewBinding with it when I should remove setContentView(binding.root) from activity second the method setSupportActionBar(binding.appBarMain.toolbar) is not found, and I couldn't use the navigation components like supportFragmentManager and setupActionBarWithNavController the third thing I couldn't"t declare this
val menuHost: MenuHost = requireActivity() in onCreateView in fragment I see it's Required: MenuHost but Found: FragmentActivity

enter image description here

menu updates

here's my MainActivity code before edits

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {


    private lateinit var appBarConfiguration: AppBarConfiguration
    private lateinit var binding: ActivityMainBinding
    private lateinit var navController: NavController
    private lateinit var postViewModel: PostViewModel
    private lateinit var navGraph: NavGraph


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)


        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        postViewModel = ViewModelProvider(this)[PostViewModel::class.java]
        postViewModel.getCurrentDestination()

        setSupportActionBar(binding.appBarMain.toolbar)

        val drawerLayout: DrawerLayout = binding.drawerLayout
        

        val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?

        if (navHostFragment != null) {
            navController = navHostFragment.navController
        }
        navGraph = navController.navInflater.inflate(R.navigation.mobile_navigation)


        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.nav_home, R.id.nav_accessory,
                R.id.nav_arcade, R.id.nav_fashion,
                R.id.nav_food, R.id.nav_heath,
                R.id.nav_lifestyle, R.id.nav_sports, R.id.nav_favorites, R.id.about
            ), drawerLayout
        )
//        setupActionBarWithNavController(navController, appBarConfiguration)
//        navView.setupWithNavController(navController)

        setupActionBarWithNavController(this, navController, appBarConfiguration)
        setupWithNavController(binding.navView, navController)



        
//        determineAdvertisingInfo()
    }


    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }

}

and this the implementation of menus in fragments

  override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {

        _binding = FragmentHomeBinding.inflate(inflater, container, false)


         setHasOptionsMenu(true)
         return binding.root
    }

....................................................................................

  override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.main, menu)
        super.onCreateOptionsMenu(menu, inflater)
        val searchManager =
            requireContext().getSystemService(Context.SEARCH_SERVICE) as SearchManager
        val searchView = menu.findItem(R.id.app_bar_search).actionView as SearchView
        searchView.setSearchableInfo(searchManager.getSearchableInfo(requireActivity().componentName))
        searchView.queryHint = resources.getString(R.string.searchForPosts)

        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(keyword: String): Boolean {
                if (keyword.isEmpty()) {
                    Snackbar.make(
                        requireView(),
                        "please enter keyword to search",
                        Snackbar.LENGTH_SHORT
                    ).show()
                }
//                itemArrayList.clear()
                if (Utils.hasInternetConnection(requireContext())) {
                    postViewModel.getItemsBySearch(keyword)
                    postViewModel.searchedPostsResponse.observe(viewLifecycleOwner) { response ->

                        when (response) {
                            is NetworkResult.Success -> {
                                hideShimmerEffect()
                                itemArrayList.clear()
                                binding.progressBar.visibility = View.GONE
                                response.data?.let {
                                    itemArrayList.addAll(it.items)
                                }
                                adapter.notifyDataSetChanged()

                            }

                            is NetworkResult.Error -> {
                                hideShimmerEffect()
                                //                    loadDataFromCache()
                                Toast.makeText(
                                    requireContext(),
                                    response.toString(),
                                    Toast.LENGTH_LONG
                                ).show()

                            }

                            is NetworkResult.Loading -> {
                                if (postViewModel.recyclerViewLayout.value == "titleLayout" ||
                                    postViewModel.recyclerViewLayout.value == "gridLayout"
                                ) {
                                    hideShimmerEffect()
                                } else {
                                    showShimmerEffect()
                                }
                            }
                        }
                    }
                } else {
                    postViewModel.getItemsBySearchInDB(keyword)
                    postViewModel.postsBySearchInDB.observe(viewLifecycleOwner) { items ->
                        if (items.isNotEmpty()) {
                            hideShimmerEffect()
                            binding.progressBar.visibility = View.GONE
                            itemArrayList.clear()
                            itemArrayList.addAll(items)
                            adapter.notifyDataSetChanged()
                        }
                    }
                }
                return false

            }


            override fun onQueryTextChange(newText: String): Boolean {
                return false
            }
        })
        searchView.setOnCloseListener {
            if (Utils.hasInternetConnection(requireContext())) {
                Log.d(TAG, "setOnCloseListener: called")
                itemArrayList.clear()
                requestApiData()
            } else {
                noInternetConnectionLayout()
            }
            false
        }


        postViewModel.searchError.observe(viewLifecycleOwner) { searchError ->
            if (searchError) {
                Toast.makeText(
                    requireContext(),
                    "There's no posts with this keyword", Toast.LENGTH_LONG
                ).show()
            }
        }

    }



   override fun onOptionsItemSelected(item: MenuItem): Boolean {
        if (item.itemId == R.id.change_layout) {
            changeAndSaveLayout()
            return true
        }
        return super.onOptionsItemSelected(item)
    }


build.gradle dependencies

dependencies {

    implementation 'androidx.core:core-ktx:1.6.0'
    implementation 'androidx.appcompat:appcompat:1.4.2'

    implementation ('com.google.android.material:material:1.6.1') {
        exclude(group: 'androidx.recyclerview',  module: 'recyclerview')
        exclude(group: 'androidx.recyclerview',  module: 'recyclerview-selection')
    }
    implementation "androidx.recyclerview:recyclerview:1.2.1"
    // For control over item selection of both touch and mouse driven selection
    implementation "androidx.recyclerview:recyclerview-selection:1.1.0"

    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0'
    implementation 'androidx.navigation:navigation-ui-ktx:2.5.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    //Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

//    //Moshi
//    implementation("com.squareup.moshi:moshi:1.13.0")
//    implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
//    kapt "com.squareup.moshi:moshi-kotlin-codegen:1.13.0"

    implementation 'com.github.bumptech.glide:glide:4.12.0'
    implementation 'org.jsoup:jsoup:1.14.1'
    implementation 'com.squareup.picasso:picasso:2.71828'
    implementation 'org.apache.commons:commons-lang3:3.8.1'
    implementation 'org.ocpsoft.prettytime:prettytime:4.0.1.Final'
    implementation "androidx.browser:browser:1.4.0"

    implementation 'androidx.multidex:multidex:2.0.1'
    configurations {
        all*.exclude group: 'com.google.guava', module: 'listenablefuture'
    }

    //Room
    implementation "androidx.room:room-runtime:2.4.2"
    kapt "androidx.room:room-compiler:2.4.2"
    implementation "androidx.room:room-ktx:2.4.2"
    androidTestImplementation "androidx.room:room-testing:2.4.2"




    //Dagger - Hilt
    implementation 'com.google.dagger:hilt-android:2.42'
    kapt 'com.google.dagger:hilt-android-compiler:2.42'


    //SDP & SSP
    implementation 'com.intuit.sdp:sdp-android:1.0.6'
    implementation 'com.intuit.ssp:ssp-android:1.0.6'

    // Shimmer
    implementation 'com.facebook.shimmer:shimmer:0.5.0'

    //firebase & analytics
    implementation platform('com.google.firebase:firebase-bom:28.4.0')
    implementation 'com.google.firebase:firebase-analytics'

    //crashlytics
    implementation 'com.google.firebase:firebase-crashlytics'
    implementation 'com.google.firebase:firebase-analytics'

    // DataStore
    implementation 'androidx.datastore:datastore-preferences:1.0.0'
    implementation("androidx.datastore:datastore-preferences-rxjava3:1.0.0")

    //admob
    implementation 'com.google.android.gms:play-services-ads:21.1.0'
    implementation platform('com.google.firebase:firebase-bom:30.2.0')

    implementation project(':nativetemplates')

    implementation("androidx.ads:ads-identifier:1.0.0-alpha04")

    // Used for the calls to addCallback() in the snippets on this page.
    implementation("com.google.guava:guava:28.0-android")

    implementation 'com.google.firebase:firebase-analytics'
    implementation("androidx.activity:activity-ktx:1.5.0")


}

Solution

  • Simplest solution from top of my head (hopefully it will work for you):

    val menuHost: MenuHost = requireActivity() as MenuHost
    

    This example is for fragment but I guess you can easily apply it to activity:

    First extend fragment with MenuProvider:

    class SomeFragment : SomethingIfYouHave(), MenuProvider { /* Your code */ }
    

    Now in onCreateView:

    val menuHost: MenuHost = requireActivity() as MenuHost
    menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
    

    Later in code override onCreateMenu and onMenuItemSelected.

    override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
       inflater.inflate(R.menu.info_menu, menu)
    }
        
    override fun onMenuItemSelected(item: MenuItem): Boolean {
        return when (item.itemId) {
            R.id.something-> {
                // Do something here
                true
            }
            else -> false
        }
    }
    

    AppCompatActivity extends FragmentActivity which extends ComponentActivity, why to use ComponentActivity directly?

    Please revert it to:

    class MainActivity : AppCompatActivity() { /* Your code here */ }
    

    I think your only problem was in this line:

    val menuHost: MenuHost = requireActivity() as MenuHost
    

    ------------------- UPDATE

    *ComponentActivity has all you need for a Compose-only app. If you need AppCompat APIs, an AndroidView which works with AppCompat or MaterialComponents theme, or you need Fragments then use AppCompatActivity.

    You are not using purely ComposeUI, so that is why you need AppCompat and not ComponentActivity.

    If you want to use ComponentActivity without AppCompat you need to get rid off supportFragmentManager.

    This would go out of the scope but please check ComposeUI Fragments if you want to switch on ComposeUI (I didn't write article).