How to update textview of a fragment inside a ViewPager (Kotlin) - android

Suppose I have got a seek bar in the activity to change the textview font size of the fragment. What method should I pass?
I have an activity. This includes a view pager. The view pager has a pager adapter. For each item in the pager adapter, we create new instance of fragment. When I drag the SeekBar, I want to pass the value onto the fragment. I have applied interface callback and also passing argument bundle. But, when it comes to implementation and testing, the font size des not change.
Would you please advise me the way to pass one value from a seek bar of an activity to a fragment within the pager adapter ?
Here is my working :
class ChapterActivity : AppCompatActivity() , ViewPager.OnPageChangeListener {
...
val listener = object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
val scaledSize = progress * 0.6 + minimumValue
println("scaledSize : $scaledSize" )
println("scaledSize : ${scaledSize.toFloat()}" )
//txt_chapter_content.setTextSize(TypedValue.COMPLEX_UNIT_DIP, scaledSize .toFloat() );
val prefs = getPreferences(Context.MODE_PRIVATE)
val ed = prefs.edit()
ed.putFloat("fontsize", scaledSize.toFloat())
ed.apply()
val myBundle = Bundle()
myBundle.putFloat("fontsize" , scaledSize.toFloat() )
mAboutDataListener!!.onDataReceived(scaledSize.toFloat())
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
}
}
chapterPagerAdapter = ChapterPagerAdapter(supportFragmentManager, chapters)
// Set the Adapter and the TabLayout forward the ViewPager
chapterViewPager.adapter = chapterPagerAdapter
chapterViewPager.addOnPageChangeListener(this);
Fragment:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout forward this fragment
val view = inflater.inflate(com.books.learn.ddy.blinkist.R.layout.content_chapter, container, false)
val titleTextView = view.findViewById<TextView>(R.id.txt_chapter_title)
val contextTextView = view.findViewById<TextView>(R.id.txt_chapter_content)
contextTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, floatSize )
override fun onDataReceived(fontSize: Float) {
contextTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, fontSize );
}

If your PagerAdapter is not FragmentStatePagerAdapter,you can obtain your Fragment(e.g. FragmentOne) and update the scaled size as follows:
val page = getSupportFragmentManager().findFragmentByTag("android:switcher:${R.id.pager}:${pagerPosition}"
if (page != null) {
((FragmentOne)page).onDataReceived(scaledSize.toFloat())
}
If not feasible,check here to know how to get the Fragment instance in the viewpager,then just call it's method in activity.

I have posted an answer to a similar question here https://stackoverflow.com/a/60427448/2102794.
Fragment
class SampleFragment : Fragment(), BaseFragmentInteraction {
override fun updateSeekBarProgress(progress: Int) {
Toast.makeText(activity!!, data, Toast.LENGTH_SHORT).show()
}
}
Interface
interface BaseFragmentInteraction {
fun updateSeekBarProgress(progress: Int)
}
SeekBar Callback:
val listener = object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
val fragmentItem = (view_pager.adapter as FragmentPagerAdapter).getItem(view_pager.currentItem)
(fragmentItem as BaseFragmentInteraction).updateSeekBarProgress(progress)
}
}

You should implement a callback interface in the fragment while keeping a reference of this callback in your activity. This way when you call a function [suppose changeFontSize()] from your activity, your fragment's implementation of this method will be called. Check this answer
Keep in mind that when using ViewPager you will also have to check for current fragment visibility. Check this answer
Hope this helps.

Related

Intent Fragment . how to used Intent In Fragment

I'm using a interface to switch from recycler View to details activitas. my interface function works. position is coming. But I can't switch to Details Activity. I think the soproduct is from context. How can I solve this problem? Thank you
class OrderFragment : Fragment() , OnMovieClickListener {
private lateinit var linearLayoutManager: LinearLayoutManager
private lateinit var adapter: RvAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view : View = inflater.inflate(R.layout.fragment_order, container, false)
val orderApiService = OrderApiService()
val api = orderApiService.getDataa(requireActivity())
api.myOrdersAssigned().enqueue(object : Callback<List<BaseModel>?> {
override fun onResponse(
call: Call<List<BaseModel>?>,
response: Response<List<BaseModel>?>
) {
val arrayOrder = response.body()
val layoutManager: LinearLayoutManager = LinearLayoutManager(activity)
recyclerViewMyOrders.setLayoutManager(layoutManager)
adapter = RvAdapter(arrayOrder as ArrayList<BaseModel>,this#OrderFragment)
recyclerViewMyOrders.setAdapter(adapter)
adapter.notifyDataSetChanged()
if(response.isSuccessful){
response.body()?.let {
}
}
}
override fun onFailure(call: Call<List<BaseModel>?>, t: Throwable) {
print(t.message.toString())
}
})
return view
}
override fun onMovieItemClicked(position: Int) {
println("Clicked : " + position.toString())
val intent = Intent(requireContext().applicationContext,DetailsActivity::class.java)
startActivity(intent)
}
}
My Interface Function :
override fun onMovieItemClicked(position: Int) {
println("Clicked : " + position.toString())
val intent = Intent(requireContext().applicationContext,DetailsActivity::class.java)
startActivity(intent)
}
Create your variable Context inside your Fragment like this :
private Context context;
Than you initialize it inside onCreateView like this :
context = view.getContext();
And insite you functio, instead of calling requireContext().applicationContext as parameter, call context
The code in on Java but you can easily convert it to Kotlin
I have a question first: why don't you use the DetailFragment instead of a whole new Activity (because your OrderFragment and Detail can be both in a single Activity)
Still want to use Activity
On your interface's override method, try to change requireContext().applicationContext to requireContext() only. Please reply me your println works or not.
Change to Fragment
I know 2 ways to move to another fragment programmatically:
parentFragmentManager.beginTransaction()
.replace<DestinationFragment>(R.id.fragmentContainer)
findNavController().navigate(action) // if you are using Navigation Components
P/s:
I'm also a Android newbie and I see something can be improved in your source code, please correct me if I have something wrong
Adapter and LinearLayoutManager don't have to be a class's property because you no longer need to use them outside API call, so just change their scope to inside onResponse()
I have suffered from doing things with view in onCreateView() (because they are not fully inflated yet ?), so considering move your logic to onViewCreated() for a safe bet.

ViewPager2 how to restore fragments when navigating back

Hi folks I have a ViewPager2 with single activity architecture. When I click a button, I swap out the ViewPager2 host fragment with another one using the Jetpack Navigation library.
This calls onDestroyView for the host fragment. When I click back, we are back to onCreateView. How can I return to the ViewPager2 I was looking at, seeing as the host fragment itself is not destroyed?
I believe based on this answer that restoring a ViewPager2 is actually impossible, not sure if this is by design or not. So what is the best practice here, assuming each fragment loads a heavy list, am I supposed to reload all the data every time a user pops the backstack into my viewpager? The only thing I can think of is to have an activity scoped ViewModel which maintains the list of data for each fragment, which sounds ridiculous, imagine if my pages were dynamically generated or I had several recycler views on each fragment....
Here is my attempt, I am trying to do the bare minimum when navigating back, however without assigning the view pager adapter again, I am looking at a blank fragment tab. I don't understand this, the binding has not died, so why is the view pager not capable of restoring my fragment?
OrderTabsFragment.kt
var adapter: TabsPagerAdapter? = null
private var _binding: FragmentOrdersTabsBinding? = null
private val binding get() = _binding!!
private var initted = false
override fun onCreate(savedInstanceState: Bundle?) {
Timber.d("OrderTabsFragment $initted - onCreate $savedInstanceState")
super.onCreate(savedInstanceState)
adapter = TabsPagerAdapter(this, Tabs.values().size)
adapter?.currentTab = Tabs.valueOf(savedInstanceState?.getString(CURRENT_TAB) ?: Tabs.ACTIVE.name)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
Timber.d("OrderTabsFragment $initted - onCreateView $savedInstanceState, _binding=$_binding")
if(_binding == null)
_binding = FragmentOrdersTabsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
Timber.d("OrderTabsFragment $initted - onViewCreated $savedInstanceState")
super.onViewCreated(view, savedInstanceState)
if(!initted) {
initted = true
val viewpager = binding.viewpager
viewpager.adapter = adapter
viewpager.isSaveEnabled = false
binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {}
override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabReselected(tab: TabLayout.Tab?) {
if (adapter?.currentTab == Tabs.FILTERED) {
showFilterBalloon(tab)
}
}
})
TabLayoutMediator(binding.tabLayout, viewpager) { tab, position ->
when (position) {
0 -> tab.text = getString(R.string.title_active).uppercase(Locale.getDefault())
1 -> tab.text =
getString(R.string.title_scheduled).uppercase(Locale.getDefault())
2 -> tab.text =
getString(R.string.title_complete).uppercase(Locale.getDefault())
}
}.attach()
}
else{
val viewpager = binding.viewpager
viewpager.adapter = adapter //Required otherwise we are looking at a blank fragment tab. The adapter rv was detached and can't be reattached?
viewpager.isSaveEnabled = false //Required otherwise "Expected the adapter to be 'fresh' while restoring state."
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Timber.d("OrderTabsFragment $initted - onSaveInstanceState")
outState.putString(CURRENT_TAB, adapter?.currentTab?.name)
}
override fun onDestroy() {
super.onDestroy()
Timber.d("OrderTabsFragment $initted - onDestroy")
binding.viewpager.adapter = null
_binding = null
adapter = null
}
enum class Tabs {
ACTIVE, SCHEDULED, COMPLETE, FILTERED
}
Edit:
Here's roughly the same questions coming up in other places 1, 2, 3

Cannot populate spinner with data from database?

I'm trying to populate a spinner with data using room, I'm getting no errors but my spinner isn't displaying anything. I think it might have something to do with how I'm calling initFirstUnitSpinnerData() in my onCreateView method? But I'm having no luck. I'm using kotlin.
Thanks in advance.
DAO:
#Query("SELECT firstUnit FROM conversion_table WHERE category LIKE :search")
fun getByCategory(search: String): LiveData<List<String>>
Repository:
fun getByCategory(search: String): LiveData<List<String>>{
return conversionsDAO.getByCategory(search)
}
View Model:
fun getByCategory(search: String): LiveData<List<String>> {
return repository.getByCategory(search)
}
Fragment:
class UnitsFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
private lateinit var mConversionsViewModel: ConversionsViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_units, container, false)
mConversionsViewModel = ViewModelProvider(this).get(ConversionsViewModel::class.java)
initFirstUnitSpinnerData()
return view
}
private fun initFirstUnitSpinnerData() {
val spinnerFirstUnit = view?.findViewById<Spinner>(R.id.firstUnitSpinner)
if (spinnerFirstUnit != null) {
val allConversions = context?.let {
ArrayAdapter<Any>(it, R.layout.support_simple_spinner_dropdown_item)
}
mConversionsViewModel.getByCategory("Distance")
.observe(viewLifecycleOwner, { conversions ->
conversions?.forEach {
allConversions?.add(it)
}
})
spinnerFirstUnit.adapter = allConversions
spinnerFirstUnit.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
Toast.makeText(requireContext(), "$allConversions", Toast.LENGTH_LONG).show()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
}
}
}
This is the kind of thing you should debug really - click on the left gutter for the first line of initFirstUnitSpinnerData (the val spinnerFirstUnit one), click the Debug App button up near the Run one, and it'll pause when it hits that breakpoint you added.
Then you can move through, step by step, looking at the values of stuff and checking if it looks right, and how the code executes. It's a super useful thing to learn and it'll save you a lot of headaches!
Anyway I'm guessing your problem is that you're calling initFirstUnitSpinnerData from inside onCreateView - the latter is called by the Fragment when it needs its layout view inflating, which you do and then return it to the Fragment.
So inside initFirstUnitSpinnerData, when you reference view (i.e. the Fragment's view, which it doesn't have yet, because onCreateView hasn't returned it yet) you're getting a null value. So spinnerFirstUnit ends up null, and when you null check that, it skips setting up the adapter.
Override onViewCreated (which the Fragment calls when it has its layout view) and call your function from there, it'll be able to access view then - see if that helps!

Kotlin: spinner onItemSelectedListener from another fragment

i have a fragment with a BottomNavigationView, a Spinner and a FrameLayout, in the FrameLayout appears a a new fragment with the BottomNavigationView.setOnNavigationItemSelectedListener, this is my code:
Fragment ValcuotaEvolFragment
class ValcuotaEvolFragment: Fragment() {
lateinit var fragment : Fragment
override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?, savedInstanceState: Bundle?): View? {
val root = inflater.inflate(R.layout.fragment_valcuota_evol, container, false)
val menuBottom: BottomNavigationView = root.findViewById(R.id.nav_view_valcuota_evol)
val spn : Spinner = root.findViewById(R.id.spnAFP)
val db = DataBaseHandler(activity!!.applicationContext)
val afpListName : ArrayList<String> = db.getAFPNames()
fragment= ValcuotaChartFragment()
val bundle = Bundle()
spn.adapter= ArrayAdapter<String>(activity!!.applicationContext,android.R.layout.simple_spinner_dropdown_item,afpListName)
spn.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
bundle.putString("afp",spn.selectedItem.toString())
}
override fun onNothingSelected(parent: AdapterView<*>) { }
}
menuBottom.setOnNavigationItemSelectedListener {
menuItem ->
when(menuItem.itemId){
R.id.nav_evolcuota_chart -> {
fragment = ValcuotaChartFragment()
}
R.id.nav_evolcuota_data -> {
fragment = ValcuotaDataFragment()
}
}
fragment.setArguments(bundle);
val transaction = childFragmentManager.beginTransaction()
transaction.replace(R.id.frame_valcuota_evol, fragment)
transaction.addToBackStack(null)
transaction.commit()
true
}
fragment.setArguments(bundle);
val transaction = childFragmentManager.beginTransaction()
transaction.replace(R.id.frame_valcuota_evol, fragment)
transaction.addToBackStack(null)
transaction.commit()
return root
}
}
I pass to the new fragment the value "afp" through a Bundle, now i need the new fragment to do something different depending on what I select in the spinner of ValcuotaEvolFragment
this is what i need:
class ValcuotaDataFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val root = inflater.inflate(R.layout.fragment_valcuota_data, container, false)
val afp = arguments!!.getString("afp")
if(afp == "something"){
doSomething()
else {
doSomethingElse
}
return root
}
}
this actually works, but only when i change the item in the BottomNavigationView i need this works when change the item in the Spinner, thx
EDIT
The EventBus solution works fine , but now i have a new problem in ValcuotaDataFragment i have a RecyclerView, so now i need fill the RecyclerView after change the item in the Spinner, this is how i do it now:
val rcViewValcuota = root. findViewById(R.id.rc_valcuota_data) as RecyclerView
var valcuota : MutableList<ValcuotaModel>
val db = DataBaseHandler(activity!!.applicationContext)
valcuota = db.getCompleteValCuota(spinnerData.selectedItem,"desc")
rcViewValcuota.adapter= ContentValcuotaMonthlyAdapter(valcuota)
i can't access the "root" from the function listenItemChange
Okay, so when you're selecting a different item from the spinner, your fragment is not aware of it unless you pass data to fragment. So for informing the fragment, you can implement the interface if you'd like. Or my favorite you can use EventBus library to pass the data.
I'll show you how you can implement EventBus.
First, add EventBus Dependency is the Gradle file:
implementation 'org.greenrobot:eventbus:3.1.1'
Okay now create a data class for passing data say SpinnerData:
data class SpinnerData(val selectedItem:String)
Then inside your itemSelected listener, pass the data using EventBus:
spn.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
// bundle.putString("afp",spn.selectedItem.toString())
//insted add following line
EventBus.getDefault().post(SpinnerData(spn.selectedItem.toString()))
}
override fun onNothingSelected(parent: AdapterView<*>) { }
}
Then inside your ValcuotaDataFragment add the following:
#Subscribe
fun listenItemChange(spinnerData: SpinnerData){
if (spinnerData.selectedItem == "something") {
doSomething()
} else {
doSomethingElse()
}
}
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
Now, whenever you change the spinner item Evenbus will be triggered and pass the data to the Subscribed method inside your fragment.
Hope this helps, let me know if you get stuck somewhere.
Edit:
This won't work if your fragment isn't initialized already.
SO keep your line inside your itemSelected listener for first time use:
bundle.putString("afp",spn.selectedItem.toString())

KOTLIN - How to set TextViews and buttons setting from Activity to fragment

I'm new on Android, in particular on Kotlin development.
How from title, i'm trying to understand how to achieve this:
I have an Activity with some buttons and textviews. I would to implement an hidden fragment opened after 5 clicks on UI. That fragment work look like the activity. I'm able to open the fragment properly and set the layout properly. I don't know how to replace buttons activity settings from activity to fragment. I have same problem with the textview. How could I achieve it?
Thanks in Advance.
Here Activity Kotlin part that open fragment:
override fun onTouchEvent(event: MotionEvent): Boolean {
var eventaction = event.getAction()
if (eventaction == MotionEvent.ACTION_UP) {
//get system current milliseconds
var time = System.currentTimeMillis()
//if it is the first time, or if it has been more than 3 seconds since the first tap ( so it is like a new try), we reset everything
if (startMillis == 0L || (time-startMillis> 3000) ) {
startMillis=time
count=1
}
//it is not the first, and it has been less than 3 seconds since the first
else{ // time-startMillis< 3000
count++
}
if (count==5) {
// Log.d("tag","start hidden layout")
// Get the text fragment instance
val textFragment = MyFragment()
val mytostring =board_status_tv.toString()
val mArgs = Bundle()
mArgs.putString(BOARDSTATE, mytostring)
textFragment.setArguments(mArgs)
// Get the support fragment manager instance
val manager = supportFragmentManager
// Begin the fragment transition using support fragment manager
val transaction = manager.beginTransaction()
// Replace the fragment on container
transaction.replace(R.id.fragment_container,textFragment)
transaction.addToBackStack(null)
// Finishing the transition
transaction.commit()
}
return true
}
return false
}
Fragment Kotlin class:
class MyFragment : Fragment(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val parentViewGroup = linearLayout
parentViewGroup?.removeAllViews()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Get the custom view for this fragment layout
val view = inflater!!.inflate(R.layout.my_own_fragment,container,false)
// Get the text view widget reference from custom layout
val tv = view.findViewById<TextView>(R.id.text_view)
// val tv1 = view.findViewById<TextView>(R.id.board_status_tv1)
// Set a click listener for text view object
tv.setOnClickListener{
// Change the text color
tv.setTextColor(Color.RED)
// Show click confirmation
Toast.makeText(view.context,"TextView clicked.",Toast.LENGTH_SHORT).show()
}
// Return the fragment view/layout
return view
}
override fun onPause() {
super.onPause()
}
override fun onAttach(context: Context?) {
super.onAttach(context)
}
override fun onDestroy() {
super.onDestroy()
}
override fun onDetach() {
super.onDetach()
}
override fun onStart() {
super.onStart()
}
override fun onStop() {
super.onStop()
}
}
Please note that you will need to get Text before converting it to string, like that in second line.
board_status_tv .getText(). toString()
val textFragment = MyFragment()
val mytostring = board_status_tv.getText().toString()
val mArgs = Bundle()
mArgs.putString(BOARDSTATE, mytostring)
textFragment.setArguments(mArgs)
Hope this will resolve your problem

Categories

Resources