I'm learning Jetpack by changing the Demo from codelabs.
What I changed is move the code of the MainActivity.kt into a fragment and jump between fragments.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(WordListFragment())
}
}
// Extension function to replace fragment
fun AppCompatActivity.replaceFragment(fragment: Fragment){
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.host,fragment)
transaction.addToBackStack(null)
transaction.commit()
}
When we click the items, we will call replaceFragment inside WordListAdapter and go to another fragment as follows:
class WordListAdapter : RecyclerView.Adapter<WordListAdapter.WordViewHolder>() {
...
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = words[position]
holder.wordItemView.text = current.word
holder.itemView.setOnClickListener {
// fire recyclerView click event
val activity = it.context as AppCompatActivity
val args = Bundle()
// Send string data as key value format
args.putString("word", current.word)
val fragment = WordDefinitionFragment()
fragment.arguments = args
activity.replaceFragment(fragment)
}
}
I just wondering if it's the right way to put replaceFragment inside the onBindViewHolder ?
In my opinion RecyclerView.Adapter should only bind immutable data and pass clicks via callbacks, and the Activity should be the one to change fragment. In my opinion you should do something like this:
class WordListAdapter(private val onViewHolderClicked: (String) -> Unit) : RecyclerView.Adapter<WordListAdapter.WordViewHolder>() {
...
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = words[position]
holder.wordItemView.text = current.word
holder.itemView.setOnClickListener {
onViewHolderClicked(current.word)
}
}
and in Activity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(WordListFragment())
}
...
fun setupRecyclerView() {
...
val adapter = WordListAdapter() { word ->
val args = Bundle()
// Send string data as key value format
args.putString("word", word)
val fragment = WordDefinitionFragment()
fragment.arguments = args
replaceFragment(fragment)
}
}
}
Related
class Crypto : Fragment(R.layout.fragment_crypto) {
private lateinit var recyclerView: RecyclerView
private lateinit var cryptolist: ArrayList<crypro_data>
private lateinit var cryptoAdapter: cryptoAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView = view.findViewById(R.id.recyclerview)
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(activity)
cryptolist = ArrayList()
cryptolist.add(crypro_data(R.drawable.ic_baseline_history_24,"dolar"))
cryptolist.add(crypro_data(R.drawable.ic_baseline_history_24,"lari"))
cryptolist.add(crypro_data(R.drawable.ic_baseline_history_24,"lira"))
cryptolist.add(crypro_data(R.drawable.ic_baseline_history_24,"sterlingi"))
cryptolist.add(crypro_data(R.drawable.ic_baseline_history_24,"dolar"))
cryptolist.add(crypro_data(R.drawable.ic_baseline_history_24,"lari"))
cryptolist.add(crypro_data(R.drawable.ic_baseline_history_24,"lira"))
cryptolist.add(crypro_data(R.drawable.ic_baseline_history_24,"sterlingi"))
cryptolist.add(crypro_data(R.drawable.ic_baseline_history_24,"dolar"))
cryptolist.add(crypro_data(R.drawable.ic_baseline_history_24,"lari"))
cryptoAdapter = cryptoAdapter(cryptolist)
recyclerView.adapter = cryptoAdapter
}
}
this is my code on a fragment where i have recyclerview
i would like to know if it would be possible to open a same fragment when clicked on any of the items in the recyclerview.
but each item has to transfer its unique id to opened fragment in order to find out which button was clicked.
To open a Fragment with a button click in a RecyclerView, you can use the setOnClickListener method on the button in your RecyclerView's adapter together with a click listener in the Fragment from where you create the adapter.
Here is an example of how you can do this in an adapter:
class ImageAdapter(private var imageModel: List<ImageModel>) : RecyclerView.Adapter<ImageAdapter.PostViewHolder>() {
...
//The click listener
var onButtonClick: ((ImageModel) -> Unit)? = null
override fun onBindViewHolder(holder: PostViewHolder, position: Int) {
...
//Set the click listener on the button you have in your RV element
holder.itemView.button.setOnClickListener {
//Clicked
onButtonClick?.invoke(imageModel[position])
}
}
}
In your Fragment, you can implement the listener and set it to the adapter and handle the transition:
lass YourFragment : Fragment() {
...
private val imageAdapter = ImageAdapter(listOf())
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
imageAdapter.onButtonClick = { imageModel ->
// Handle the button click here
// open a new fragment
val fragment = AnotherFragment()
val fragmentManager = childFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
// Pass object as extra
bundle.putParcelable("key", imageModel)
fragment.arguments = bundle
fragmentTransaction.replace(R.id.fragment_container, fragment)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
}
}
}
And in the AnotherFragment, you can retrieve the object from the arguments property in the onCreate method:
class AnotherFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Retrieve object from extra
val myObject = arguments?.getParcelable<MyObject>("key")
}
}
In this example, the class ImageModel,should implement the Parcelable interface.
Happy Coding!
I want to pass data to fragment so when items in recycler adapter clicked it pass item name (sample[position].text1) fetched from firebase to fragment. I tried bundle, interface but getting error in both methods.I searched on internet but not find anything which solve my problem. mainActivity(splash screen) is only Activity in my App rest are fragments.
I used inner class method, I'm getting result but in another fragment where this adapter attached and I don't want it there.
Problem: pass sample[position].text1 to fragment so I can pass it to db.collection("here") to fetch data from Firebase.
Adapter
class dashboard_gridlayout_adapter(
private val sampledata: ArrayList<daxhboard_gridlayout_data>
): Adapter<dashboard_gridlayout_adapter.dashboard_viewholder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): dashboard_viewholder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.dashboard_gridlayout_single_item_design, parent, false)
return dashboard_viewholder(itemView)
}
override fun onBindViewHolder(holder: dashboard_viewholder, position: Int) {
Glide.with(holder.itemView).load(sampledata[position].imageResource)
.placeholder(R.drawable.ic_baseline_history_icon)
.into(holder.imageView)
holder.textView.text = sampledata[position].text1
holder.itemView.setOnClickListener {
val appCompatActivity = it.context as AppCompatActivity
appCompatActivity.supportFragmentManager.beginTransaction()
.replace(R.id.Activity_frag_container, service_providers_list())
.addToBackStack(null)
.commit()
}
}
override fun getItemCount() = sampledata.size
inner class dashboard_viewholder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.dashboard_adapter_image
val textView: TextView = itemView.dashboard_adapter_text
}
}
Fragment
class service_providers_list : Fragment(){
private var db = FirebaseFirestore.getInstance()
private lateinit var service_list_recycler: RecyclerView
var servlist = ArrayList<service_provider_list_data>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.service_providers_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
getserviceproviderdata()
service_list_recycler = service_provider_recycle_view.findViewById(R.id.service_provider_recycle_view)
service_provider_recycle_view.layoutManager = LinearLayoutManager(this.requireContext())
service_provider_recycle_view.setHasFixedSize(true)
}
private fun getserviceproviderdata() {
db.collection("Barber").orderBy("dist")
.get()
.addOnSuccessListener { documents ->
servlist.clear()
for (document in documents) {
val imgurl = document.data["imageResource"].toString()
val prov_name = document.data["provider_name"].toString()
val prov_address = document.data["provider_address"].toString()
val prov_rate = document.data["provider_rating"].toString()
val prov_dist = document.data["provider_distance"].toString()
servlist.add(service_provider_list_data(imgurl, prov_name, prov_address, prov_rate, prov_dist))
service_provider_recycle_view.adapter = service_provider_list_adapter(servlist)
}
}
.addOnFailureListener { exception ->
Log.e("serf", "Error getting documents: ", exception)
}
}
}
MainActivity (It's a splash screen)
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
#Suppress("DEPRECATION")
Handler().postDelayed(
{
supportFragmentManager.beginTransaction().replace(R.id.Activity_frag_container,Login_Screen()).commit()
},
1500
)
}
}
I solved this problem
just add new parameter(need to pass) inside replace in adapter
holder.itemView.setOnClickListener {
val datashares = sampledata[position].text1
val appCompatActivity = it.context as AppCompatActivity
appCompatActivity.supportFragmentManager.beginTransaction()
.replace(R.id.Activity_frag_container, service_providers_list(datashares))
.addToBackStack(null)
.commit()
}
and inside fragment just add
class service_providers_list(datashares: String) Fragment(){
//variable declaration
private var datasharae = datashares
(inside function where i wnt to add code i.e getserviceproviderdata() )
fun getserviceproviderdata() {
db.collection(datasharae)
.............
..............
......rest code.....
.........}
I would like to ask you for help. I am writing an application that uses MVVM and LiveData architecture. Inside ViewPager I have 3 fragments displaying data that comes from ViewModel. And I noticed that after connecting the viewModel to the activity and to the fragment, the data is updated only when the activity is started, but then Observe does not update the data even though the data has changed. After calling the next query to the server, inside onDataSet I send the appropriate time and obtains JSON data from the server, which it parses and passes to ViewModel. Why Fragment updates data only once in the beginning and nothing changes after?
This is the activity that hosts the fragments
class MainActivity : AppCompatActivity(), DatePickerDialog.OnDateSetListener {
private lateinit var currencyViewModel: CurrencyViewModel
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
private lateinit var navigationView: NavigationView
private lateinit var floatingActionButton: FloatingActionButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fm = supportFragmentManager
currencyViewModel = ViewModelProvider
.AndroidViewModelFactory(application)
.create(CurrencyViewModel::class.java)
viewPager = findViewById(R.id.viewPager)
tabLayout = findViewById(R.id.tabLayout)
navigationView = findViewById(R.id.navigationView)
floatingActionButton = findViewById(R.id.floatingActionButton)
val viewPagerAdapter = CurrencyViewPagerAdapter(this)
viewPager.adapter = viewPagerAdapter
TabLayoutMediator(tabLayout
,viewPager
,TabLayoutMediator.TabConfigurationStrategy {
tab, position ->
when(position){
0 -> tab.text = "Tabela A"
1 -> tab.text = "Tabela B"
2 -> tab.text = "Tabela C"
}
}).attach()
floatingActionButton.setOnClickListener {
val dialog = CalendarFragment()
dialog.show(fm, "DatePickerDialog")
}
}
override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {
//Convert year,month,day to millisecounds
val c = Calendar.getInstance()
c.set(year,month,dayOfMonth)
val dayInMillis = c.time.time
val today = Calendar.getInstance()
if(checkIsDateAfterToday(today, c)){
CoroutineScope(Dispatchers.Main).launch {
currencyViewModel.setTableA(dayInMillis)
}
}
}
This is ViewModel common for activity and fragment
class CurrencyViewModel : ViewModel() {
private val repository = CurrencyRepository()
val tableA: MutableLiveData<Array<TableA>> by lazy {
MutableLiveData<Array<TableA>>().also {
loadTableA(Date().time)
}
}
private fun loadTableA(time: Long) {
CoroutineScope(Dispatchers.Main).launch {
val loadedData = CoroutineScope(Dispatchers.IO).async {
repository.getTableA(time)
}.await()
tableA.value = loadedData
}
}
fun setTableA(time: Long){
loadTableA(time)
}
}
And that's the fragment which displays data in recyclerView
class TableAFragment : Fragment() {
private lateinit var currencyViewModel: CurrencyViewModel
private lateinit var recyclerViewA: RecyclerView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_table_a, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
currencyViewModel = ViewModelProvider.AndroidViewModelFactory
.getInstance(requireActivity().application)
.create(CurrencyViewModel::class.java)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerViewA = view.findViewById(R.id.recyclerView_A)
recyclerViewA.layoutManager = LinearLayoutManager(requireContext())
currencyViewModel.tableA.observe(viewLifecycleOwner, androidx.lifecycle.Observer{
val nbpAdapter = NBPAdapter(it)
recyclerViewA.adapter = nbpAdapter
})
}
}
Your instantiation of ViewModel is incorrect.
Should be
currencyViewModel = ViewModelProvider(this).get<CurrencyViewModel>() // lifecycle-ktx
and in Fragment:
currencyViewModel = ViewModelProvider(requireActivity()).get<CurrencyViewModel>() // lifecycle-ktx
I create a click listener in a fragment. That Listener sets a livedata value in the viewmodel. I'm observing that value and when it changes, I put its value in a bundle and pass it in a fragment transaction to the next fragment.
This all works. But when I navagate back to this fragment(with the back button), the observer is called again, and it navigates away.
I have tried:
- removing the observer.. using extension function as demonstrated in this post
https://code.luasoftware.com/tutorials/android/android-livedata-observe-once-only-kotlin/
setting the viewmodel and listener to null after in the observer closure
-tried to reset the value of the livedata string
Setting the listener:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val backpackImage = view?.findViewById(R.id.image_backpack) as ImageView
val poloImage = view?.findViewById(R.id.polo_image) as ImageView
val pantsImage = view?.findViewById(R.id.pants_image) as ImageView
val socksImage = view?.findViewById(R.id.socks_image) as ImageView
val sweatshirtImage = view?.findViewById(R.id.sweatshirt_image) as ImageView
categoryOfImageClickListner = View.OnClickListener {
view->
when(view.id){
R.id.image_backpack -> viewModel?.setCategory(Categories.BACKPACKS.storeCategories)
R.id.polo_image ->viewModel?.setCategory(Categories.POLOS.storeCategories)
R.id.pants_image ->viewModel?.setCategory(Categories.PANTS.storeCategories)
R.id.socks_image ->viewModel?.setCategory(Categories.SOCKS.storeCategories)
R.id.sweatshirt_image ->viewModel?.setCategory(Categories.SWEATSHIRTS.storeCategories)
}
}
backpackImage.setOnClickListener(categoryOfImageClickListner)
socksImage.setOnClickListener(categoryOfImageClickListner)
pantsImage.setOnClickListener(categoryOfImageClickListner)
poloImage.setOnClickListener(categoryOfImageClickListner)
sweatshirtImage.setOnClickListener(categoryOfImageClickListner)
}
---------------------
the
Observer:
override fun onActivityCreated(savedInstanceState: Bundle?) {
...
...
var categoryObservable :LiveData<String> = viewModel?.getCategoryForNavigation()!!
categoryObservable.observeOnce(viewLifecycleOwner, Observer {
categoryObservable.removeObservers(this)
it.let {
var args = Bundle()
args.putString("CATEGORY", it)
val productFrag = ProductListFragment()
productFrag.arguments = args
val fragmentManger = activity?.let {
if (it is FragmentActivity)
it.supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, productFrag, "PRODUCTFRAG")
.addToBackStack(null)
.commit()
}
}
})
````````````
the
Viewmodel
````````````
class HomeViewModel : ViewModel() {
var liveDataCategory: MutableLiveData<String> = MutableLiveData()
fun getCategoryForNavigation(): LiveData<String> =liveDataCategory // observe this in fragment.. and nav onChange
fun ResetCategoryForNav()= liveDataCategory.postValue(Categories.SWEATSHIRTS.storeCategories)
fun setCategory( category: String){
liveDataCategory.postValue(category)
Log.d("ONCLICK", "Inside Onclick Viewmodel")
}
I am trying to use navigation controller. I have a bottom navigation view. that located on my MainActivity, and it is initiated using the code below on :
class MainActivity : AppCompatActivity() {
lateinit var navController : NavController
lateinit var logoHeaderImageView : ImageView
var toolbarMenu : Menu? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
logoHeaderImageView = findViewById(R.id.header_lakuin_image_view)
navController = Navigation.findNavController(this,R.id.nav_main_host_fragment)
setupBottomNavMenu(navController)
setupActionBar(navController)
}
fun setupBottomNavMenu(navController: NavController) {
NavigationUI.setupWithNavController(bottom_navigation_view,navController)
}
fun setupActionBar(navController: NavController) {
setSupportActionBar(main_activity_toolbar)
main_activity_toolbar.title = ""
val appBarConfiguration = AppBarConfiguration(
setOf(
// set some destination as the top hierarchy destination, to make the up button doesn't show.
R.id.destination_home,
R.id.destination_order,
R.id.destination_favourite,
R.id.destination_cart,
R.id.destination_profile
))
NavigationUI.setupWithNavController(main_activity_toolbar,navController,appBarConfiguration)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_main_toolbar, menu)
toolbarMenu = menu
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
}
}
here is the look of my bottom navigation view:
So I want to pass data from my HomeFragment (destination home) to OderFragment (destination order). I usually using bundle or safeArgs to pass data like the code below:
var bundle = bundleOf("amount" to amount)
view.findNavController().navigate(R.id.confirmationAction, bundle)
but I don't know where to place that code, If I want to pass data from my HomeFragment to OderFragment
When using NavigationUI.setupWithNavController(bottom_navigation_view,navController) (or, if you're using the navigation-ui-ktx Kotlin extension bottom_navigation_view.setupWithNavController(navController)), you can't pass any custom arguments to destinations - an important part of global navigation is that they always take you to the same screen in the same state.
Generally, you should be holding data like the current amount separately from Navigation arguments - whether it is in a persisted database, SharedPreferences, or some other location that would survive process death, allowing users to continue with what they're doing even after restarting their phone, etc.
However, if you must use Navigation arguments for this, you can set the default argument for your destination ahead of time (i.e., whenever your amount changes):
NavDestination orderDestination = navController.graph.findNode(R.id.destination_order)
orderDestination.addArgument("amount", NavArgument.Builder()
.setType(NavType.FloatType)
.setDefaultValue(amount)
.build())
Afterwards, your BottomNavigationView triggering R.id.destination_order will automatically include that argument, along with your new amount value, by default.
You can use a shared ViewModel between fragments:
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
model.selected.observe(this, Observer<Item> { item ->
// Update the UI
})
}
}
more information can be found here:
https://developer.android.com/topic/libraries/architecture/viewmodel#sharing
Just like that:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val bundle = Bundle()
bundle.putString("myname","Hussnain")
return when(item.itemId){
R.id.aboutFragment ->{
navController.navigate(R.id.aboutFragment,bundle)
return true
}else -> {
NavigationUI.onNavDestinationSelected(item,navController) || super.onOptionsItemSelected(item)
}
}
}
Navigation:
<fragment android:id="#+id/aboutFragment"
android:name="com.cinderellaman.general.ui.fragments
.AboutFragment"
android:label="about_fragment"
tools:layout="#layout/about_fragment">
<argument android:name="myname" app:argType="string"/>
</fragment>
fragment:
val args: AboutFragmentArgs by navArgs()
name.text = args.myname
Post data:
Fragment fragment = new OderFragment();
Bundle bundle = new Bundle();
bundle.putString("key", "value");
fragment.setArguments(bundle);
FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame_container, fragment);
transaction.addToBackStack(null);
transaction.commit();
Receive data:
Bundle bundle = this.getArguments();
if (bundle != null) {
myInt = bundle.getString("key");
}
Call Nav contoller from current fragment. And pass data as a bundle
Ex:
First fragment -
val bundle = Bundle()
bundle.putString("crs",crs); //This is the passing parameter
findNavController().navigate(R.id.action_Classes_to_AddUpdateStudents,bundle)
//Amend your parameter as a second argument
Second fragment -
crs = arguments?.getString("crs").toString()
//Retrieve the data as u usually do with an activiy