Kotlin - How to get value from button choice in Dialog? - android

I am in the process of creating a golf scorecard app.
Every hole's score is an empty textView, and that textView has a setOnClickListener to open the score picker dialog.
I want to get the score value from the score picker dialog.
Here is the dialog interface:
https://i.stack.imgur.com/VNVc1.png
Each button is corresponding to a score.
I know that each button will need a setOnClickListener, but my knowledge is limited about everything else afterward.
So my question is how to return that score value so I can display it in that specific the textView and add it to the player's total? Any suggestions would be very helpful.
Thank you for your time.

You can implement custom listner, that listner you need to
below code is for demo,
implement in diaglog fragment
//score for current hole dialog
class SomeDialog:DialogFragment() {
private var dl: CustomListener? = null
// interface to detect dialog close event
interface CustomListener {
fun closeEvent(id: String, valueToPass: Int)
}
fun customListerTrig(l: CustomListener) {
dl = l
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
button1.setOnClickListener {
dl?.closeEvent("golfDiag", 1)
this.dismiss()
}
button2.setOnClickListener {
dl?.closeEvent("golfDiag", 2)
this.dismiss()
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_some_dialog, container, false)
}
}
How i called and retrived button click event
val common = SomeDialog()
common.customListerTrig(object : CommonInfoDialog.CustomListener {
override fun closeEvent(id: String, buttonValueFromDialog: Int) {
// todo usebuttonValueFromDialog
}
})
//use fragment according where this dialog will be called.
common.show(this.childFragmentManager, "golfDiag")

May be this code is easy for you. Use Android dialog API to make your work easier.
/**
* e.g.
showPickScoreDialog(requireContext()) { score ->
Toast.makeText(context, "score[$score]", Toast.LENGTH_LONG).show()
}
*/
fun showPickScoreDialog(context: Context, callback: (Int) -> Unit) {
val defaultCheckedScore = -1
val scoreList = getScoreList()
MaterialAlertDialogBuilder(context)
.setTitle("Score For Current Hole:")
.setSingleChoiceItems(scoreList, defaultCheckedScore) { dialog: DialogInterface?, which: Int ->
val score = scoreList[which].toInt()
callback(score)
dialog?.dismiss()
}
.setNegativeButton("Cancel", null)
.show()
}
private fun getScoreList(): Array<String> {
val list = mutableListOf<String>()
for (i in 1..12) {
list.add(i.toString())
}
return list.toTypedArray()
}

So my question is how to return that score value so I can display it in that specific the textView and add it to the player's total? Any suggestions would be very helpful.
https://developer.android.com/guide/topics/ui/dialogs#PassingEvents

Related

TextView.text changes but display of its display on a fragment isn't updating

I had a working app that does some arithmetic functionality that is out of the scope of the question, then I wanted to add more functionality to it, so i separated the layout into activity and fragment in order to later add other fragments that will do extra functions.
yet when I separated the layout taking some buttons along with a TextView (R.id.Result) to the new fragment, the text property of the TextView still updates as expected, but the display stays the same, always showing the initialization value initially assigned to it on its creation time.
I confirmed that the objects are the same as I expected them to be during runtime verified through logcat, what I need OFC is for the TextView display to update when I change its text property, numberInsertAction is called from the buttons properly and send proper data.
Important Note: below is only the relevant parts of code, it is much larger and I know what you see below can be simplified but it is built this way because of other classes and functionality that aren't shown below, if you need to see or ask about something outside the below code please do, yet again I only included the related part only and removed the business functionality.
Thanks in advance.
just to reiterate: numberInsertAction(view: View) is the entry point/function called by the buttons on the fragment.
MainActivity.kt
class MainActivity : AppCompatActivity(), AddObserverToActivity {
private lateinit var binding: ActivityMainBinding
private lateinit var stateManager: StateManager
override fun onCreate(savedInstanceState: Bundle?) {
//initialize layout
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val activityRoot = binding.root
setContentView(activityRoot)
stateManager = StateManager()
}
override fun addResultObserver(observer: Observer) {
Log.d(TAG, "addObserver! ${observer.toString()} ${observer::class.toString()}")
StateManager.addDisplayObserver(observer)
}
fun numberInsertAction(view: View) {
if (view is Button) {
StateManager.enterDigit(view.text.toString())
}
}
}
CalculatorFragment.kt
class CalculatorFragment : Fragment() {
companion object {
fun newInstance() = CalculatorFragment()
}
private lateinit var binding: FragmentCalculatorBinding
private lateinit var mainActivityHandle: AddObserverToActivity
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d(TAG, "onCreateView")
binding = FragmentCalculatorBinding.inflate(inflater, container, false)
return inflater.inflate(R.layout.fragment_calculator, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d(TAG, "using on view created")
mainActivityHandle = context as AddObserverToActivity
Log.d(TAG, "${binding.Result} ${(binding.Result)::class.simpleName.toString()}")
Log.d(TAG, mainActivityHandle::class.toString())
mainActivityHandle.addResultObserver(DisplayPanel(binding.Result))
}
}
StateManager.kt
class StateManager : Observable() {
private val displayBuffer = DisplayBuffer(DecimalVariable("0"))
fun enterDigit(digit: String) {
Log.d(TAG, "enterDigit: $digit, $currentState")
displayBuffer.insertDigit(digit)
}
fun addDisplayObserver(observer: Observer) {
Log.d(TAG, "addDisplayObserver: $observer")
displayBuffer.addObserver(observer)
}
private fun doNotify(Notified: Any) {
Log.d(TAG, "doNotify: $Notified")
setChanged()
notifyObservers(Notified)
}
}
DisplayBuffer.kt
class DisplayBuffer(initializationValue: SomeClass) : Observable() {
private var initialValue = initializationValue
private var resultString = "0"
var value = initialValue
set(value) {
Log.d(TAG, "setter: $value")
field = value
doNotify()
}
fun set(value: String) {
Log.d(TAG, "set: $value")
this.value = value as Int
}
private fun doNotify() {
Log.d(TAG, "doNotify")
setChanged()
notifyObservers(value.toString())
}
fun insertDigit(digit: String) {
Log.d(TAG, "insertDigit: $digit result: $resultString")
resultString = resultString + digit
Log.d(TAG, "new value: $resultString")
setChanged()
notifyObservers(resultString)
}
}
DisplayPanel.kt
class DisplayPanel(calculationTextView: TextView) : Observer {
private val displayField: TextView = calculationTextView
private val maxDigits = 16
private fun setDisplay(text: String) {
Log.d(TAG, "setDisplay: $text")
if (text.length <= maxDigits) {
displayField.text = text
//displayField.invalidate()
}
}
override fun update(observable: Observable?, targetObjects: Any?) {
Log.d(TAG, "update: $this $observable, $targetObjects")
setDisplay(targetObjects as String)
}
}
Add binding.lifecycleOwner = viewLifecycleOwner in onCreateView or onViewCreated method.
was answered by #Mike M in Comments:
In CalculatorFragment,
He instructed me to change
return inflater.inflate(R.layout.fragment_calculator, container, false) to return binding.root.
as the problem was that this function inflated two instances of the fragment calculator layout and returned the later while it used the former as observer.
to qoute #Mike-M:
The inflater.inflate() call is creating a new instance of that layout that is completely separate from the one that FragmentCalculatorBinding is creating and using itself.
FragmentCalculatorBinding is inflating the view internally, which is why it is passed the inflater in its inflate() call.

How to display Toast.makeText result in another page textView using kotlin

//This is my scanner barcode code using kotlin
override fun receiveDetections(detections: Detector.Detections) {
val barcodes = detections.detectedItems
if (barcodes.size() == 1) {
scannedValue = barcodes.valueAt(0).rawValue
runOnUiThread {
cameraSource.stop()
Toast.makeText(this#InsertStockInActivity, "value- $scannedValue", Toast.LENGTH_SHORT).show()
finish()
}
}else
{
Toast.makeText(this#InsertStockInActivity, "value- else", Toast.LENGTH_SHORT).show()
}
}
//This is my input page
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentInputStockInBinding.inflate(inflater, container, false)
binding.btnScanBarcode.setOnClickListener{ nav.navigate(R.id.insertStockInActivity)}
return binding.root
}[enter image description here][1]
i thnk you should extract your toast text as a variable and pass it to another page ( i assume that your mean are to another fragment/activity or to another screen)
private lateinit var toastText:String
...
if (barcodes.size() == 1) {
...
toastText = scannedValue
...
} else {
toastText = "value- else"
}
Toast.makeText(this#InsertStockInActivity, toastText , Toast.LENGTH_SHORT).show()
}
and pass toastText to another page via Intent or safe-args if you are using Jetpack Navigation
You could use a view model, live data, or another (static) object to hold your results in a variable. Along the same lines, you can create a show toast function in another class and just pass the context of the fragment or activity that you are in. For example a fragment context could be requireContext().
fun showToast(context: Context?, message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}

Custom DialogFragment with AlertDialog returns EditText as ""

I have a custom DialogFragment that I'm using to capture user input that I will create a database entry with. I'm using EditText in an AlertDialog. I am trying to use a single activity for my application and the original tutorial I was studying was using multiple activities and intents but that seems outdated for most cases.
When I debug I find that the EditText is returning "" and is showing up as empty when I call TextUtils.isEmpty() in the MainActivity onDialogPositiveClick.
I've done a lot of combing through the forms here and I'm confused by:
1)many of the answers I find are in Java and not Kotlin
2)many mention onCreate but do not specify onCreateView vs. onCreateDialog or if there's just an onCreate that I need to override.
I have researched this and found answers that confuse me a bit about when and if I need to inflate the layout. This current itteration I didn't inflate it at all. I just set it in the AlertDialog builder.
Maybe it's the interface I'm not understanding. How am I supposed to pass information between the dialog and MainActivity? The interface seems to pass the dialog itself but I seem to be missing something when it comes to getting the EditText from the dialog.
My custom DialogFragment
class NewSongFragment : DialogFragment() {
lateinit var listener: NewSongListener
lateinit var editNewSong: EditText
lateinit var editBPM: EditText
interface NewSongListener {
fun onDialogPositiveClick(dialog: DialogFragment)
fun onDialogNegativeClick(dialog: DialogFragment)
}
/** The system calls this to get the DialogFragment's layout, regardless
of whether it's being displayed as a dialog or an embedded fragment. */
/*
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout to use as dialog or embedded fragment
return inflater.inflate(R.layout.fragment_new_song, container, false)
}
*/
// Override the Fragment.onAttach() method to instantiate the NoticeDialogListener
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
listener = context as NewSongListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException((context.toString() +
" must implement NewSongListener"))
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(it)
//add inflater
//val inflater = requireActivity().layoutInflater;
//val view = inflater.inflate(R.layout.fragment_new_song, null)
builder
.setView(R.layout.fragment_new_song)
.setCancelable(true)
.setNegativeButton(R.string.cancel,DialogInterface.OnClickListener { dialog, id ->
dialog?.cancel()
})
.setPositiveButton(R.string.button_save,
DialogInterface.OnClickListener {dialog, _ ->
listener.onDialogPositiveClick(this)
})
// Create the AlertDialog object and return it
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
}
My MainActivity
class MainActivity : AppCompatActivity(),NewSongFragment.NewSongListener {
private val songViewModel: SongViewModel by viewModels {
SongViewModelFactory((application as SongApplication).repository)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//create view
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
val adapter = ItemAdapter(this,
ItemAdapter.OnClickListener { rating -> songViewModel.insertRating(rating) }
)
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(this)
//initialize data
songViewModel.allSongs.observe(this) { song ->
// Update the cached copy of the songs in the adapter.
song.let { adapter.submitList(it) }
}
// Use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
recyclerView.setHasFixedSize(true)
//add song button
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
showNewSongDialog()
}
}
private fun showNewSongDialog() {
// Create an instance of the dialog fragment and show it
val dialog = NewSongFragment()
dialog.show(supportFragmentManager, "NewSongFragment")
}
override fun onDialogPositiveClick(dialog: DialogFragment) {
// User touched the dialog's positive button
val editNewSong = dialog.view?.findViewById<EditText>(R.id.newSongTitle)
val editBPM = dialog.view?.findViewById<EditText>(R.id.newSongBpm)
if(TextUtils.isEmpty(editNewSong?.text)){
}else{
val newSong = Song(editNewSong?.text.toString(),100)
songViewModel.insertSong(newSong)
val rating = Rating(System.currentTimeMillis(),newSong.songTitle, 50)
songViewModel.insertRating(rating)
}
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
// User touched the dialog's negative button
}
}
You are adding the layout with a resource identifier, so your call to get the view is returning null. (Why? The view is inflated internally and just handled differently.) Since you are using the AlertDialog to collect data, you will have to add an inflated view.
I am also going to suggest that you change the interface to hide the details of the dialog; There is no reason for the main activity to know the internal structure of the dialog. It just needs the song title and BPM and maybe some other stuff. You will find the code a little easier to understand and maintain.
Here is a slight rework. This code just captures the song title, but it can easily be extended to include other data as well.
In NewSongFragment:
interface NewSongListener {
fun onDialogPositiveClick(songTitle: String)
fun onDialogNegativeClick(dialog: DialogFragment)
}
val inflater = requireActivity().layoutInflater;
val view = inflater.inflate(R.layout.fragment_new_song, null)
builder
.setView(view)
.setCancelable(true)
.setNegativeButton(R.string.cancel, DialogInterface.OnClickListener { dialog, id ->
dialog?.cancel()
})
.setPositiveButton(R.string.button_save)
{ dialog, _ ->
Log.d("Applog", view.toString())
val songTitle = view?.findViewById<EditText>(R.id.newSongTitle)?.text
listener.onDialogPositiveClick(songTitle.toString())
}
In MainActivity.kt
override fun onDialogPositiveClick(songTitle: String) {
// songTitle has the song title string
}
Android dialogs have some quirks. Here are a number of ways to do fragment/activity communication.
Because you are adding the dialog as a Fragment, you should use onCreateView to inflate the view, rather than trying to add a view in onCreateDialog.

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!

Dialog with a counter in Android Studio

I have an application that performs writing by NFC on a card, depending on the number that has passed will perform a number or other reads. I guess I do this with a simple for loop, the problem is that I do not know where to put that for loop. I give you an example of the class:
class HomeFragment : Fragment(), OnClickDetailsMonuments {
private lateinit var homeFragmentViewModel: HomeFragmentViewModel
private lateinit var homeMonumentsAdapter: MonumentsAdapter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
inflater.inflate(R.layout.home_fragment, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews()
getObservers()
onClickGoToMaps(view)
homeFragmentViewModel.loadJsonFromRetrofit()
}
private fun onClickGoToMaps(view: View) {
fbGoToMaps.setOnClickListener {
view.findNavController().navigate(R.id.googleMapsMonumentsFragment)
}
}
private fun getObservers() {
viewModel.getNFCInfo().observe(viewLifecycleOwner, Observer {
when(it.status){
AsyncResult.Status.SUCCESS -> {
NFCProvider.initialize(context, it.data)
}
AsyncResult.Status.ERROR -> {
}
AsyncResult.Status.LOADING -> {
}
}
})
}
override fun onClickFavListener(monuments: MonumentsVO) {
homeFragmentViewModel.updateDatabaseFavoriteCheckFromViewModel(monuments.id, monuments.fav)
}
override fun onClickRowListenerExtras(monuments: MonumentsVO, position: Int, extras: FragmentNavigator.Extras) {
val bundle = bundleOf(BUNDLE_MONUMENT to monuments)
view?.findNavController()?.navigate(R.id.detailsMonumentsFragment, bundle, null, extras)
}
override fun onClickRowListener(monuments: MonumentsVO, position: Int) {}
private fun initViews() {
homeFragmentViewModel = ViewModelProviders.of(this).get(HomeFragmentViewModel::class.java)
rvHomeFragmentMonumentsRetrofit.setHasFixedSize(true)
val layoutManager = LinearLayoutManager(context)
rvHomeFragmentMonumentsRetrofit.layoutManager = layoutManager
}
override fun onResume() {
super.onResume()
(activity as AppCompatActivity).supportActionBar?.show()
}
private fun showDialog(title: String, process: String, titleButton: String) {
val dialog = CustomSuccessDialog(title, process, titleButton)
dialog.show()
}
override onNewIntentResult(intent) {
val message = NFCProvider.retrieveNfcMessage(intent)
if(message) {
showDialog("Correct reading", "1 de 4", "Continue")
} else {
showDialog("Error reading", "1 de 4", "Retry")
}
}
}
As you can see the problem is that when I start the NFC is when I pass the message. I get this message from an internet service and it returns the number of cards that I have to record, which is the one I have to go through, and since on the one hand I have the onNewIntent that I need for the NFC and on the other hand the observer, I don't know how to do it to put it all together and that every time I write an NFC card I get the correct dialogue and when I continue to the next one, the number of the dialogue increases: 1 of 4, 2 of 4, 3 of 4, etc. See if you can give me a hand. Thank you very much.
You can save the instance of the dialog and update your textView in your dialog like this:
if(message) {
if(mDialog?.isShowing == false) {
showDialog("Correct reading", "1 de 4", "Continue")
} else {
mDialog.updateText("$count de 4")
count ++
}
}
And your showDialog method will look something like this:
private fun showDialog(title: String, process: String, titleButton: String) {
mDialog = CustomSuccessDialog(title, process, titleButton)
mDialog.show()
}

Categories

Resources