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

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

Related

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

How to save data when i press back button

I have made an app in kotlin through the android studio, Now I have used ViewModels to save UI data while phone rotation(configuration change), i also used onSaveInstanceState to save data while pressing back button but it's not working.
The code is below
fragOne.kt
class fragOne : Fragment() {
private lateinit var viewModel: fragViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
if(savedInstanceState!=null){
with(savedInstanceState) {
viewModel.num=getInt("number")
}
}
// Inflate the layout for this fragment
var binding = DataBindingUtil.inflate<FragmentFragoneBinding>(
inflater,
R.layout.fragment_fragone,
container,
false
)
viewModel = ViewModelProviders.of(this).get(fragViewModel::class.java)
// function to update number
fun updateNumber()
{
binding.number.text="${viewModel.num}"
}
updateNumber()
// setting on Click listener for add button
binding.add.setOnClickListener()
{
viewModel.addFive()
updateNumber()
}
// setting on on Click Listener for minus button
binding.minus.setOnClickListener()
{
viewModel.minusOne()
updateNumber()
}
return binding.root
}
override fun onSaveInstanceState(outState: Bundle) {
// Save the user's current game state
outState?.run {
putInt("number",viewModel.num)
}
// Always call the superclass so it can save the view hierarchy state
if (outState != null) {
super.onSaveInstanceState(outState)
}
}
}
ViewModelclass
class fragViewModel:ViewModel()
{
// Initializing num=0
var num=0
// Functions to add five or subtract one
fun addFive()
{
num=num+5
}
fun minusOne()
{
num=num-1
}
}
please tell me because data is not saved when I press back
You can override onBackPressed to do your state saving:
How to implement onBackPressed() in Fragments?
Remember to call super, so that is does also do the back command!
You could also do like the below:
// This callback will only be called when MyFragment is at least Started.
val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
// Handle the back button event
}
Really good read: https://developer.android.com/guide/navigation/navigation-custom-back
Back navigation is how users move backward through the history of screens they previously visited. All Android devices provide a Back button for this type of navigation, so you should not add a Back button to your app’s UI. Depending on the user’s Android device, this button might be a physical button or a software button.
Ref:
How to show warning message when back button is pressed in fragments
Example:
Ensure your Activity extends AppCompatActivity
class MyFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(fragViewModel::class.java)
val prefs = activity.getSharedPreferences("Key")
int num = prefs.get("number", -999)
if(num != -999) {
viewModel.num = num
}
val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
prefs.edit().putInt("number", viewModel.num).apply()
}
}
...
}

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())

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

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.

Clear Searchview Text on navigation.

I have an Activity with two child fragments Timeline and Milestones. Both these fragments contain listviews populated by a custom Cursor adapter
Here is a graphical Representation:
Now when I am on TIMELINE and I open up the searchview, I type something all is good I get the desired result. But when I navigate from Timeline to Milestones with some text in the searchview the searchview does not get cleared, so I get filtered results on the Milestones page too and acccording to the paramaters I provided in Timeline.
I am using AppCompact lib to develop my ActionBar. The tabs in there are not ActionBar Tabs but simple SlidingTabLayout.
So far I have tried using
getActivity().supportInvalidateOptionsMenu(); in onResume() of both the fragments, does not work.
I have tried searchView.setQuery("",false) - does not work and randomly gives me a NPE.
SO what do I miss here?
You can take a look on my example, where I showed how to control searchView between fragments.
Firstly. You need to create BaseFragment, which works with context of activity with appBarLayout.
open class BaseFragment: Fragment() {
lateinit var rootActivity: MainActivity
lateinit var appBarLayout: AppBarLayout
lateinit var searchView: androidx.appcompat.widget.SearchView
override fun onAttach(context: Context) {
super.onAttach(context)
this.rootActivity = context as MainActivity
appBarLayout = rootActivity.findViewById(R.id.app_bar_layout)
searchView = rootActivity.findViewById(R.id.search_input)
}
override fun onResume() {
super.onResume()
resetAppBarLayout()
}
private fun resetAppBarLayout() {
appBarLayout.elevation = 14f
}
fun setupSearch(query: String) {
searchView.visibility = View.VISIBLE
searchView.clearFocus()
when(query.isNotEmpty()) {
true -> {
searchView.setQuery(query, true)
searchView.isIconified = false
}
false -> {
searchView.isIconified = true
searchView.isIconified = true
}
}
}
fun hideSearchKeyboard() {
context?.let {
KeyboardHelper.hideSearchKeyboard(it, searchView.findViewById(R.id.search_src_text))
}
}
fun hideSearch() {
searchView.visibility = View.GONE
searchView.clearFocus()
}
}
Secondly. Inherit your fragments from BaseFragment, override onResume() method and control searchView in your fragments by calling methods from BaseFragment. Something like this.
class FragmentA : BaseFragment() {
private var searchQuery = ""
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.fragment, container, false)
val textView: TextView = root.findViewById(R.id.textView)
textView.text = "Fragment A"
return root
}
override fun onResume() {
super.onResume()
setupSearch()
}
private fun setupSearch() {
searchView.setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener {
override fun onQueryTextChange(newText: String?): Boolean {
when(newText.isNullOrEmpty()) {
true -> searchQuery = ""
false -> searchQuery = newText
}
return true
}
override fun onQueryTextSubmit(query: String?): Boolean {
hideSearchKeyboard()
return true
}
})
super.setupSearch(searchQuery)
}
}
Full example you can find here https://github.com/yellow-cap/android-manage-searchview

Categories

Resources