Change MaterialToolbar title from Fragment - Kotlin - android

I have implemented a MaterialToolbar and I want to change the title and functionality of the buttons, depending on which fragment is active. But I've been trying for two days and I can't get access to toolbar from fragment.
It doesn't work with any option:
activity?.actionBar?.title
(activity as AppCompatActivity).supportActionBar?.title
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/materialToolbar"
style="#style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="#drawable/ic_menu" />
<fragment
android:id="#+id/fragmentContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/materialToolbar"
app:navGraph="#navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.navigation.NavigationView
android:id="#+id/navigationView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#color/primary_ultra_light"
app:headerLayout="#layout/header_menu_drawer"
app:menu="#menu/menu_drawer" />
</androidx.drawerlayout.widget.DrawerLayout>
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.materialToolbar)
supportActionBar?.title = "Test"
}
}
HomeFragment.kt
class HomeFragment: Fragment() {
private lateinit var binding: HomeFragmentBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.actionBar?.title = "Home 1" /* actionBar == null */
/* supportActionBar == null */
(activity as AppCompatActivity).supportActionBar?.title = "Home 2"
}
Result of the code that does not comply with the expected
The idea is to remove the top block of the fragment (Calculators) and use the Toolbar (Test). For this I need to be able to adapt the toolbar for each fragment.
Possible solution <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
So far I have solved it with a ViewModel observer. I don't know if it's a correct solution, but it works. Any comment is welcome, both to confirm that it is a valid solution and if it is not.
ToolbarVM.kt
class ToolbarVM(app: Application) : AndroidViewModel(app) {
private val _title = MutableLiveData<String>()
var title: LiveData<String> = _title
fun setTitle(newTitle: String){
_title.value = newTitle
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val toolbarVm: ToolbarVM by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.materialToolbar)
val titleObserver = Observer<String> { newTitle ->
binding.materialToolbar.title = newTitle
}
toolbarVm.title.observe(this, titleObserver)
}
}
HomeFragment
/*You have to instantiate the ToolbarVm and call the setTitle() method*/
private lateinit var toolbarVm: ToolbarVM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
toolbarVm = activity?.let { ViewModelProvider(it)[ToolbarVM::class.java] }!!
}
// Call the method where necessary
toolbarVm.setTitle("Calculators")

From the MainActivity you can directly use:
binding.materialToolbar.title = "Test"
while when calling from a fragment, you can do the following:
activity?.findViewById<Toolbar>(R.id.materialToolbar)?.title = "Test"

I have solved it thanks to Razvan's answer. It works if I call it from the onViewCreated() function, but it doesn't work the first time the default fragment is loaded with navGraph. So I have placed it in onResume()
HomeFragment.kt
override fun onResume() {
super.onResume()
(activity as AppCompatActivity).supportActionBar?.title = getString(R.string.calculators)
}

Related

On Click event Does not working with Data Binding in Android Studio

I am learning DataBinding in android studio. But I am facing a problem with binding a ModelView. I want to bind a function with a button on click event. I set a function in the model view. I want to update my text view with on click event of my button. But When I click the button my text is not updating. I can not understand what I have done wrong.
XML layout:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="model"
type="com.example.jetpack.MainModelView" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{model.title}"
android:textSize="28sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="Update Text"
android:onClick="#{()-> model.update()}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tv" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Main Model View:
class MainModelView : ViewModel() {
var title: String = " This is My Application"
fun update() {
title = "I am Changed"
Log.d("UPDATE", "update successfully from main model view")
}
}
Main Activity:
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
lateinit var mainModelView: MainModelView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mainModelView = ViewModelProvider(this).get(MainModelView::class.java)
binding.model = mainModelView
}
}
My app Image:
thanks in advance for helping.
The title variable in ViewModel needs to be a ObservableField or LiveData otherwise you xml will never know when it's value got updated –
class MainViewModel : ViewModel() {
var text = MutableLiveData(" Welcome to my application ")
fun updateText() {
text.value = " Text is updated successfully "
}
}
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// Creating MainViewModel object
mainViewModel = MainViewModel()
// Binding mainViewModel variable
// with MainViewModel object
binding.mainViewModel = mainViewModel
// passing LifeCycleOwner to binding object
binding.lifecycleOwner = this
}
}

Android: Viewpager2 gets stuck half way

When trying to swipe between any of my viewpager2 fragments, the view gets stuck at 75% of the transition.
Picture
Swiping from first fragment to second fragment:
Swiping from second fragment to first fragment:
Viewpager_Layout
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.tabs.TabLayout
android:id="#+id/tl_on_boarding_item"
style="#style/ShapeAppearanceOverlay.EXAMPLE.ShopTablayout"
android:layout_width="0dp"
android:layout_height="16dp"
android:layout_marginBottom="24dp"
android:background="#android:color/transparent"
app:layout_constraintBottom_toBottomOf="#+id/viewPager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:tabBackground="#drawable/unselected_shop_item_tab"
app:tabIndicator="#drawable/selected_on_boarding_item_tab"
app:tabIndicatorColor="#color/primary"
app:tabIndicatorFullWidth="false"
app:tabIndicatorGravity="center"
app:tabIndicatorHeight="8dp"
app:tabMaxWidth="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
Viewpager_Fragment
#AndroidEntryPoint
class OnBoardingFragmentHolder #Inject constructor(
private val onBoardingViewPagerAdapter: OnBoardingViewPagerAdapter,
private val mediator: TabLayoutHelper,
) : InvisibleBottomNavFragment<FragmentOnBoardingHolderBinding>() {
override val bindingInflater: (LayoutInflater) -> FragmentOnBoardingHolderBinding
get() = FragmentOnBoardingHolderBinding::inflate
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initTablayout()
}
private fun initTablayout() {
binding.viewPager.adapter = onBoardingViewPagerAdapter
binding.viewPager.offscreenPageLimit = 2
mediator.init(binding.tlOnBoardingItem, binding.viewPager)
}
override val onDestroyView: () -> Unit
get() = {
binding.viewPager.adapter = null
mediator.onDestroyView()
}
}
InvisibleBottom Fragment
abstract class InvisibleBottomNavFragment<out T : ViewBinding> : BindingFragment<T>() {
override fun onAttach(context: Context) {
super.onAttach(context)
postStickyEvent(MainActivityBusEventBottomNav(toBeClosed = true,
fromClass = "InvisibleBottomNavFragment.kt"))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postStickyEvent(MainActivityBusEventBottomNav(toBeClosed = true,
fromClass = "InvisibleBottomNavFragment.kt"))
}
override fun onResume() {
super.onResume()
postStickyEvent(MainActivityBusEventBottomNav(toBeClosed = true,
fromClass = "InvisibleBottomNavFragment.kt"))
}
}
Viewpager
class OnBoardingViewPagerAdapter #Inject constructor(
fragment: Fragment
) : FragmentStateAdapter(fragment) {
private companion object {
private const val FRAGMENT_ITEM_COUNT = 3
}
override fun getItemCount(): Int = FRAGMENT_ITEM_COUNT
override fun createFragment(position: Int): Fragment = when (position) {
0 -> OnBoardingFirstFragment()
1 -> OnBoardingSecondFragment()
2 -> OnBoardingThirdFragment()
else -> throw IllegalStateException("$position can not be satisfied")
}
}
What I find most weird is that when I fast swipe between the fragments (e.g not holding the mouse button up to the end and just swiping) everything works fine.

How to pass multiple views as parameters in BindingAdapter in Data binding android?

I have one relative layout and one ImageView. I want to set visibility based on Image loading like if image loads successfully then imageview is visible and if some error occurs relative layout is visible. How can I manage this scenario in data binding using BindingAdapter ?
Your question is not clear so I don't know if it will help you.
These are steps to implement
1: Create parameters in BindingAdapter.kt
#BindingAdapter("showLoading")
fun View.showLoading(loading: Boolean) {
if (loading) {
visible()
} else {
gone()
}
}
#BindingAdapter("showError")
fun View.showError(error: Boolean) {
if (error) {
visible()
} else {
gone()
}
}
fun View.gone() {
this.visibility = View.GONE
}
fun View.visible() {
this.visibility = View.VISIBLE
}
2: Use parameters [app:showLoading="#{viewModel.showLoading}"] and [app:showError="#{viewModel.showError}"] created in file XML
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.xxx.xxx.MainViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#EAEAE2"
android:orientation="vertical">
<Button
android:id="#+id/btnAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ShowLoading" />
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:showLoading="#{viewModel.showLoading}" />
<RelativeLayout
android:id="#+id/viewError"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#color/colorPrimary"
app:showError="#{viewModel.showError}" />
</LinearLayout>
</layout>
3: Create showError and showLoading variables in Viewmodel.
And assign the viewModel variable to the binding.
class MainViewModel: ViewModel() {
val showError: MutableLiveData<Boolean> = MutableLiveData()
val showLoading: MutableLiveData<Boolean> = MutableLiveData()
init {
showError.postValue(false)
showLoading.postValue(true)
}
}
class MainActivity : AppCompatActivity() {
private val viewModel = MainViewModel()
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.lifecycleOwner = this
binding.viewModel = viewModel
initViews()
}
private fun initViews() {
binding.btnAdd.setOnClickListener {
viewModel.showError.postValue(true)
viewModel.showLoading.postValue(false)
}
}
}

Android Kotlin fragment's onCreate not called

I am trying to display Fragment but I do not know why I can't do that. Here is my code:
The main_activity.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.MainActivity">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fragment_container" />
</androidx.constraintlayout.widget.ConstraintLayout>
Followed by MainActivity.kt:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(R.layout.main_activity)
supportFragmentManager.beginTransaction().apply {
replace(R.id.fragment_container, CurrencyListFragment())
addToBackStack(null)
commit()
}
}
}
The layout currency_list_fragment.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.CurrencyListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<ProgressBar
android:id="#+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Another class CurrencyListFragment.kt:
class CurrencyListFragment : Fragment(), MainContract.View {
private val restModel: RestModel = RestModel()
lateinit var mainPresenter: MainPresenter
var isLoading: Boolean = false
var apiResponseList: MutableList<ApiResponse> = arrayListOf()
lateinit var itemAdapter: ItemAdapter
var handler: Handler = Handler()
lateinit var _layoutManager: LinearLayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.e("a","a")
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Log.e("a","a")
return inflater.inflate(R.layout.currency_list_fragment,container,false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.e("a1","a1")
_layoutManager = LinearLayoutManager(activity)
mainPresenter = activity?.let { MainPresenter(this, it.getPreferences(Context.MODE_PRIVATE)) }!!
val currentDate = mainPresenter.convertDate()
mainPresenter.makeACall("2021-07-22")
addScrollerListener()
}
private fun addScrollerListener() {
rvItem.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(_rvItem: RecyclerView, newState: Int) {
super.onScrollStateChanged(_rvItem, newState)
if (!isLoading) {
if (!_rvItem.canScrollVertically(1)) {
loadMore()
isLoading = true
}
}
}
})
}
private fun loadMore() {
//notify adapter using Handler.post() or RecyclerView.post()
handler.post {
apiResponseList.add(ApiResponse("", "", listOf(Currency2("", 0f)), true))
itemAdapter.notifyItemInserted(apiResponseList.size - 1)
}
handler.postDelayed({
apiResponseList.removeAt(apiResponseList.size - 1)
val listSize = apiResponseList.size
itemAdapter.notifyItemRemoved(listSize)
val nextLimit = listSize + 1
for (i in listSize until nextLimit) {
apiResponseList.add(
ApiResponse("", "2020-06-11", listOf(Currency2("a", 2f)), false)
)
}
itemAdapter.notifyDataSetChanged()
isLoading = false
}, 2000)
}
override fun onDestroy() {
super.onDestroy()
restModel.cancelJob()
}
}
Neither of onCreate(), onCreateView() nor onViewCreated() are not called (I suppose that because Logs texts are not shown in Logcat. I guess I added everything you need to solve my problem but if I am wrong and you need something more just ask. Why is that so?
Thank you very much for help!
It turned out I overrided wrong onCreate method on my class extending AppCompatActivity.

How to resolve "recyclerView must not be null"

I am trying to Implement Recycler View in Activity after parsing data using Retrofit.But the problem is it shows Recycler view cannot be null even after initializing inside onCreate method before accessing.
Mainactitivty.kt
class MainActivity : AppCompatActivity() {
lateinit var myRecyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
retroInstance = RetroInstance()
val instance = retroInstance.getInstance()
val api = instance.create(RetroInterface::class.java)
val callAll=api.getAllDetail()
callAll.enqueue(object :retrofit2.Callback<ModelAll>{
override fun onFailure(call: Call<ModelAll>, t: Throwable) {
Toast.makeText(applicationContext,t.message,Toast.LENGTH_LONG).show()
}
override fun onResponse(call: Call<ModelAll>, response: Response<ModelAll>) {
val allDetail=response.body()!!
myRecyclerView=findViewById(R.id.recyclerView)
myRecyclerView.layoutManager=LinearLayoutManager(this#MainActivity)
myRecyclerView.adapter=CoronaAdapter(allDetail)
}
})
}
}
Activity with recycler view included
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AllCountries">
<SearchView
android:id="#+id/searchView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="2dp"
android:background="#drawable/custom_search"
android:elevation="5dp"
android:queryHint="Search here..."
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/searchView" />
</androidx.constraintlayout.widget.ConstraintLayout>
Logcat
https://gist.github.com/devpawann/af3cef9d204a6f99cd7ed11937684fa2
EDIT:- Issue solved, the mistake was that I initializes recycler view in another activity class
You can maybe try something like this:
class MainActivity : AppCompatActivity() {
private var allDetails: MutableList<ModelAll> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
retroInstance = RetroInstance()
val instance = retroInstance.getInstance()
val api = instance.create(RetroInterface::class.java)
val callAll=api.getAllDetail()
//Init your recyclerview
val myRecyclerView = findViewById(R.id.recyclerView)
myRecyclerView.layoutManager=LinearLayoutManager(this)
val coronaAdapter = CoronaAdapter(allDetails)
myRecyclerView.adapter = adapter
callAll.enqueue(object :retrofit2.Callback<ModelAll>{
override fun onFailure(call: Call<ModelAll>, t: Throwable) {
Toast.makeText(applicationContext,t.message,Toast.LENGTH_LONG).show()
}
override fun onResponse(call: Call<ModelAll>, response: Response<ModelAll>) {
if(response.isSucessful()){
allDetails = response.body()!!
coronaAdapter.notifyDataSetChanged()
}
}
})
}
EDIT: The issue was that the recyclerView was in another layout that the one inflated.
Your recyclerView has not been instantiated(currently it is referring to null).
Add this line
recyclerview = findViewById(R.id.recyclerView)
Or if you are using Android Extensions then make sure you are using correct recyclerview

Categories

Resources