When trying to make a popup dialog with paired bluetooth devices in a spinner, my app crashes upon opening. See this code for the xml layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp">
<Spinner
android:id="#+id/spinner_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="#+id/bConnectBtn"
android:text="CONNECT"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
In the code below here, I call a function that checks for bonded devices and then puts it in the spinner:
private fun onBluetoothEnabled() {
val bondedDevices = bluetoothAdapter?.bondedDevices
if (bondedDevices != null) {
val bondedAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, bondedDevices.map { it.name })
bondedAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner_devices.adapter = bondedAdapter
bConnectBtn.setOnClickListener {
val device = bondedDevices.toList()[spinner_devices.selectedItemPosition]
setupClient(device)
}
}
}
Here I show the dialog:
val bluetoothView = layoutInflater.inflate(R.layout.bluetoothdialog, null)
val bluetoothDialog = AlertDialog.Builder(this#MainActivity)
bluetoothDialog.setTitle("Paired Devices")
bluetoothDialog.setView(bluetoothView)
bluetoothDialog.setCancelable(false)
bluetoothDialog.setNeutralButton("TEMP CLOSE") { _, _ -> }
bluetoothDialog.show()
Some extra details about this, this works just fine when the spinner is in the main activity xml, but when I put the spinner in a popup dialog xml file, it crashes upon start. When I add ? or !! to spinner_devices?.adapter it works but doesn't fill the spinner with the bonded devices which makes sense because it allows null now.
When I debug my code, I can see that the bondedApapter gets filled with paired bluetooth devices, but when it gets to the spinner_devices.adapter it is null. Any guesses on what I am doing wrong?
There is some object initialization when you tries to bind adapter from AlertDialog. Checkout the below code that is working fine.
val bluetoothView = layoutInflater.inflate(R.layout.bluetoothdialog, null)
val bluetoothDialog = AlertDialog.Builder(this#MainActivity)
bluetoothDialog.setTitle("Paired Devices")
bluetoothDialog.setView(bluetoothView)
bluetoothDialog.setCancelable(false)
bluetoothDialog.setNeutralButton("TEMP CLOSE") { _, _ ->}
bluetoothDialog.show()
**//get views from inflated layout
val spinnerDevices = bluetoothView.findViewById(R.id.spinner_devices)
val bConnectBtn = bluetoothView.findViewById(R.id.bConnectBtn)**
//bind adapter
val bondedAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, bondedDevices.map { it.name })
bondedAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinnerDevices.adapter = bondedAdapter
bConnectBtn.setOnClickListener {
val device = bondedDevices.toList()[spinnerDevices.selectedItemPosition]
Toast.makeText(this#MainActivity,"Connection to:${device.name}",Toast.LENGTH_SHORT).show()
}
For those wondering. I was able to fix it and made it a bit more usefull by being able to click on a bluetooth device and make it connect. See code below for full code:
private fun showPairedDevicesPopup() {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
val pairedDevices: Set<BluetoothDevice> = bluetoothAdapter.bondedDevices
// Create a list of device names
val deviceNames = ArrayList<String>()
pairedDevices.forEach { deviceNames.add(it.name) }
// Create an array adapter to display the list of device names
val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, deviceNames)
// Build the alert dialog
val builder = AlertDialog.Builder(this)
builder.setTitle("Paired Devices")
builder.setCancelable(false)
builder.setAdapter(adapter) { _, which ->
// Get the selected device
val selectedDevice = pairedDevices.elementAt(which)
// Attempt to connect to the selected device
val socket: BluetoothSocket?
try {
socket = selectedDevice.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"))
socket.connect()
} catch (e: IOException) {
// Failed to connect to the device
e.printStackTrace()
}
}
builder.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
// Show the alert dialog
val dialog = builder.create()
dialog.show()
}
Related
I'm adding chips into chipGropup programatically like this
XML
<com.google.android.material.chip.ChipGroup
android:id="#+id/chipGroup"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:singleSelection="true"
app:selectionRequired="true"
app:singleLine="true">
</com.google.android.material.chip.ChipGroup>
This is the function to add chips and contains the listener
fun addChips(Names: List<String>){
Names.forEach {
val chip = Chip(requireContext())
val drawable = ChipDrawable.createFromAttributes(requireContext(), null, 0, R.style.Widget_MaterialComponents_Chip_Choice)
chip.setChipDrawable(drawable)
chip.text = it
chip.isClickable = true
chip.isCheckable = true
binding.chipGroup.isSingleSelection = true
chip.id = ViewCompat.generateViewId()
binding.chipGroup.addView(chip)
}
binding.chipGroup.forEach { child ->
(child as? Chip)?.setOnCheckedChangeListener { _, _ ->
makeAction()
}
}
}
fun makeAction(){
//val id = binding.chipGroup.checkedChipId //NOT WORKING
val ids = binding.chipGroup.checkedChipIds
val categoryNames = mutableListOf<String>()
ids.forEach { id ->
categoryNames.add(binding.chipGroup.findViewById<Chip>(id).text.toString())
}
viewModel.chipFilter(categoryNames, args.orderToEdit.product.sections.toMutableList())
}
I have two problems.
One is that I can’t use checkedChipId because returns -1, so i need to use checkedChipIds. The strange thing is that I define chipgroup and chips as singleSelection.
The other problem is when I click one chip, entered two times to the setOnCheckedChangeListener
Secondly, is there any way to start the fragment with the first chip selected?
Thanks for all!
I have a custom dialog in my android project and it was working fine. After I removed kotlin extension from the project, I have modified my code as follows but there is some issue with the Views in the custom dialog. Codes etTitle.visibility = View.GONE and val newRequest = etDetail.text.toString() didn't work as I expected. It didn't hide the view etTitle and the value in the EditText etDetail is not picked also, it always returns emplty even when there is some value.
private lateinit var bindingDialogLayout: CustomDialogBinding
fun specialRequestDialog(currentRequest: String?) {
bindingDialogLayout = CustomDialogBinding.inflate(layoutInflater)
val dialogLayout = layoutInflater.inflate(R.layout.custom_dialog, null)
val etTitle = bindingDialogLayout.etTitle
val etDetail = bindingDialogLayout.etDetails
etTitle.visibility = View.GONE
etDetail.setText(currentRequest)
MaterialAlertDialogBuilder(this)
.setTitle("What is your special request?")
.setCancelable(false)
.setPositiveButton("Save") { dialog, which ->
val newRequest = etDetail.text.toString()
if (newRequest.isEmpty()) {
showErrorSnackBar("Type in if you have any special request, else hit cancel", true)
} else {
addButton.visibility = View.GONE
deleteButton.visibility = View.VISIBLE
}
}
.setNegativeButton("Cancel") { dialog, which ->
dialog?.dismiss()
}
.setView(dialogLayout)
.show()
}
You set the wrong view to the dialog.
Use this instead:
.setView(bindingDialogLayout.root)
I have multiple option select and I need to get array of selected options but all I get is latest option selected.
Code
class PublishActivity : AppCompatActivity() {
var selectedTags: List<String>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_publish)
pTags.setOnClickListener {
var tagIds = ArrayList<String>()
val tagOptions = ArrayList<String>()
for (i in tags) {
tagOptions.add(i.title)
tagIds.add(i.id)
}
var checkedItems = ArrayList<Int>()
checkedItems.forEach{ index -> tagIds[index + 1] }
MaterialAlertDialogBuilder(this)
.setTitle(resources.getString(R.string.project_tags))
.setMultiChoiceItems(tagOptions.toTypedArray(), null) { dialog, which, checked ->
if (checked) {
checkedItems.add(which)
} else if (checkedItems.contains(which)) {
checkedItems.remove(Integer.valueOf(which))
}
// Respond to item chosen
pTags.setText("${checkedItems.size} tags selected")
}
.setPositiveButton(resources.getString(R.string.ok)) { dialog, which ->
for (i in checkedItems) {
Log.d("eeee1", tagOptions[i])
selectedTags = listOf(tagOptions[i])
}
}
.setNeutralButton(resources.getString(R.string.clear)) { dialog, which ->
pTags.text = null
pTags.hint = "0 tag selected"
if (checkedItems.size > 0) {
checkedItems.clear()
}
}
.show()
}
}
}
Log.d("eeee1", tagOptions[i]) returns such data in logcat
D/eeee1: 3D Printing
D/eeee1: 3D Architecture
D/eeee1: .NET/Mono
D/eeee1: ActionScript
but in my selectedTags I get only D/eeer1: [ActionScript]
It supposed to give me something like this D/eeer1: ["3D Printing", "3D Architecture", ".NET/Mono", "ActionScript"]
PS: what I'm actually look to achieve here is to get id of those selected items instead of their names that's why I have var tagIds = ArrayList<String>() but if that's not possible to achieve as long as it just return array of all names (like sample above) it's fine by me as well.
Any idea?
The following code sets your variable to a list with a single item. So you just overwrite your variable over and over again
selectedTags = listOf(tagOptions[i])
you need:
//Declaration
var selectedTags: MutableList<String> = mutableListOf()
...
// In loop
selectedTags.add(tagOptions[i])
You could also do it with a more functional approach:
//Declaration
var selectedTags: List<String>? = listOf()
...
// Skip the loop and use the map function
.setPositiveButton(resources.getString(R.string.ok)) { dialog, which ->
selectedTags = checkedItems.map{ tagOptions[it] }
}
To get the Id's instead of the titles you should just be able to use your tagIds instead of tagOptions. Just make sure that you get your typing right. The selectedTags list needs to be of the same type as tag.id.
You are getting only last inserted value because you are creating fresh list when ok button is clicked and assigning it to selectedTags. Problem at selectedTags = listOf(tagOptions[i]) line of your code.
Solution:
Declare a single list and put selected values into it. Like :
val selectedTags = arrayListOf<String>()
then use below code inside ok button click:
.setPositiveButton("Ok") { dialog, which ->
for (i in checkedItems) {
//selectedTags = listOf(tagOptions[i])
selectedTags.add(tagOptions[i])
}
}
I'm trying to make a mobile application with a button that should create an Alert dialog with a search bar in it, However for some reason i can't get the MultiAutoCompleteTextView to work with it as it keeps giving me KotlinNullPointerExceptions. and adding nullpointer safety will just skip the setting the adapter.
addItem.setOnClickListener {
val builder = AlertDialog.Builder(this).create()
val alertview = View.inflate(this, R.layout.content_searchproduct, null)
val itemsList = listOf("Item1","items2","Btem3","btem4")
builder.setView(alertview)
val textfield =builder.findViewById<MultiAutoCompleteTextView>(R.id.textidplaceholder)
val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, itemsList)
textfield!!.setAdapter(adapter)
textfield.threshold = 1
builder.textidplaceholder.setTokenizer(MultiAutoCompleteTextView.CommaTokenizer())
builder.setButton(AlertDialog.BUTTON_POSITIVE,"Add") { _: DialogInterface?, _: Int -> searchProducts(textfield.text.toString()) }
builder.setButton(AlertDialog.BUTTON_NEGATIVE,"Cancel") { _: DialogInterface?, _: Int -> }
builder.show()
}
Do this :
val textfield =alertview.findViewById<MultiAutoCompleteTextView>(R.id.textidplaceholder)
instead of :
val textfield =builder.findViewById<MultiAutoCompleteTextView>(R.id.textidplaceholder)
I have found a solution and its attached as an answer
I'm currently having trouble populating a layout in a dialog with information from my adapter. The data is fetched from the API and passed into my data class but since the recyclerview that I'm trying to reference is in the Dialog's layout file, not in the file i'm using to call said dialog, the view just returns an null.
Here is my code for context.
CheckboxActivity.kt (Just the callback) people_list is returning null
private val callbackGETUSERS = object : Callback<List<Users>> {
override fun onFailure(call: Call<List<Users>>, t: Throwable) {
Log.e("API-GET-USERS", "Problem GETTING USERS", t) }
override fun onResponse(call: Call<List<Users>>, response: Response<List<Users>>) {
val result = UsersResult(response.body() ?: return run {
Log.e("API-ME", "Problem calling USERS")
})
peopleList = result
people_list.adapter = ManagePeopleAdapter(result)
}
}
d_manage_people.xml (the dialog resource file)
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp">
<TextView
android:id="#+id/manage_people_title"
android:gravity="center"
android:height="24dp"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
style="#style/Subtitle1"
android:text="Create Item"
android:layout_marginBottom="16dp"
android:layout_gravity="center"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/people_list"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</androidx.recyclerview.widget.RecyclerView>
and here is my error
java.lang.IllegalStateException: people_list must not be null
By the way I'm using a plugin that allows me to not use findViewById
Any help would be appreciated :)
You can show dialog after API call. Something like below
private val callbackGETUSERS = object : Callback<List<Users>> {
override fun onFailure(call: Call<List<Users>>, t: Throwable) {
Log.e("API-GET-USERS", "Problem GETTING USERS", t) }
override fun onResponse(call: Call<List<Users>>, response: Response<List<Users>>) {
val result = UsersResult(response.body() ?: return run {
Log.e("API-ME", "Problem calling USERS")
})
peopleList = result
this#CheckboxActivity.showDialog()
}
}
private fun showDialog() {
val dialog = AlertDialog.Builder(this)
val view = layoutInflater.inflate(R.layout.d_manage_people, null)
view.manage_people_title.text = "Manage People"
people_list = view.findById(R.id.people_list)
people_list.adapter = ManagePeopleAdapter(peopleList)
dialog.setView(view)
dialog.show() }
}
And just call API on button click.
onPeopleClicked(view: View) {
dataRetriever.getUsers(callbackGETUSERS, getAuth(), listID)
}
The issue happened to be with me not attaching the view's context
Instead of calling the Recycler View in the CallBack like
people_list
and then setting the adapter from there, I just had the callback call a function that then created the dialog and I used context from that to reach the RecyclerView
val dialog = AlertDialog.Builder(this)
val view = layoutInflater.inflate(R.layout.d_manage_people, null)
dialog.setView(view)
view.manage_people_title.text = "Manage People"
val adapter = ManagePeopleAdapter(result)
view.people_list.adapter = adapter
view.people_list.layoutManager = LinearLayoutManager(this)