Capture Dialog Box Edit Text Changes in Fragment - android-studio

I’m in Android Studio version Chipmunk with one patch. I’m writing in Kotlin, a language I find is just beautiful. I have a dialog box over a fragment where I want to capture the changes in the edit text box as they happen. I’ve tried with straightforward code found here in Stack Overflow but I think the fragment can’t view the dialog box.
Following is code used:
private fun dlgFind() {
try {
//val sTxt: EditText = findViewById(R.id.txtStock)
val sTxt = binding.txtMeds
val spannable: Spannable = SpannableString(sTxt.text.toString())
sTxt.setText(spannable.toString()) // clears highlighted text
val dialog = Dialog(requireContext(), R.style.RoundedCornersDialogFind)
dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND) // makes frag text readable
dialog.setContentView(R.layout.dlg_find)
// tried to max dialog window to full width of screen
//dialog.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val btnFind = dialog.findViewById(R.id.btnGo) as ImageButton
val txtFind = dialog.findViewById(R.id.editFind) as EditText
txtFind.isFocusableInTouchMode = true
txtFind.isFocusable = true
//txtFind.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
txtFind.requestFocus()
txtFind.postDelayed({
txtFind.requestFocus()
val imm = context?.getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.showSoftInput(txtFind, InputMethodManager.SHOW_IMPLICIT)
}, 1)
btnFind.setOnClickListener { dialog.dismiss()
gsFindBoxTxt = txtFind.text.toString()
sTxt.requestFocus()
//sTxt.setText(spannable.toString()) // clears highlighted text
findWithDlg() // method finds strings
}
//dialog.setOnCancelListener { editHours() }
dialog.window?.setGravity(Gravity.BOTTOM)
dialog.show()
} catch (e: Exception) {
val methodName = object {}.javaClass.enclosingMethod?.name
Toast.makeText(context, methodName.toString(), Toast.LENGTH_LONG).show()
}
}
Attempt to read from dialog box in fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
try {
val sEditTxt = view.findViewById<EditText>(R.id.editFind)
sEditTxt?.doOnTextChanged { _, _, _, _ ->
Toast.makeText(context, "!", Toast.LENGTH_LONG).show() }
if (sEditTxt?.text != null) {
sEditTxt.doAfterTextChanged {
Toast.makeText(context, "!", Toast.LENGTH_LONG).show()
findWithDlg()
}
}
} catch (e: Exception) {
Toast.makeText(context, e.toString(), Toast.LENGTH_LONG).show()
}
Note I have tried boilerplate TextWatcher code too and I can't return view in onCreate because I'm using binding throughout the fragment. The return in onCreate is to the binding.root. And even in onDestroyView the last line is _binding = null. Maybe remove all binding and revert to using views?
I've also tried these in the dialog method (function) to no avail yet...

Yes! I figured out how to listen to the text changes. I set following in the dialog method:
dialog.setOnShowListener { txtFind.doOnTextChanged { _, _, _, _ ->
Toast.makeText(context, "Text Change", Toast.LENGTH_LONG).show()
}
}

Related

CameraX When I press Take Photo button, cant enter OnImageSavedCallback

I tried to check issue with breakpoints but at this: "object : OnImageSavedCallback{
" line, debugger can't go further and just exits the function without any error. I hope someone can show me the part that I'm missing.
I also already added the permission requests to AndroidManifest.xml for writing and reading media on storage.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private var imageCapture:ImageCapture? = null
private lateinit var outputDirectory: File
private lateinit var cameraExecutor:ExecutorService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
outputDirectory = getOutputDirectory()
cameraExecutor = Executors.newSingleThreadExecutor()
if(allPermissionGranted()){
Toast.makeText(this, "Camera Permissions are Granted", Toast.LENGTH_SHORT).show()
startCamera()
}
else
{
ActivityCompat.requestPermissions(
this, Constants.REQUIRED_PERMISSIONS,
Constants.REQUEST_CODE_PERMISSIONS
)
}
binding.btnTakePhoto.setOnClickListener {
takePhoto()
}
}
private fun getOutputDirectory(): File{
val mediaDir = externalMediaDirs.firstOrNull()?.let { mFile ->
File(mFile, resources.getString(R.string.app_name)).apply {
mkdirs()
}
}
return if(mediaDir != null && mediaDir.exists())
mediaDir else filesDir
}
private fun takePhoto(){
val imageCapture = imageCapture?: return
val photoFile = File(
outputDirectory,
SimpleDateFormat(Constants.FILE_NAME_FORMAT,
Locale.getDefault()).format(System.currentTimeMillis()) + ".jpg")
val outputOption = ImageCapture.OutputFileOptions.Builder(photoFile).build()
imageCapture.takePicture(
outputOption, ContextCompat.getMainExecutor(this),
object : OnImageSavedCallback{
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile)
val msg = "Photo Saved"
Toast.makeText(
this#MainActivity,
"$msg $savedUri",
Toast.LENGTH_LONG
).show()
}
override fun onError(exception: ImageCaptureException) {
Log.e(Constants.TAG, "onError: ${exception.message}",exception)
}
}
)
}
private fun startCamera(){
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build().also {
mPreview->
mPreview.setSurfaceProvider(
binding.viewFinder.surfaceProvider
)
}
imageCapture = ImageCapture.Builder().build()
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try{
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture
)
}
catch (e: Exception){
Log.d(Constants.TAG, "startCamera Fail:", e)
}
}, ContextCompat.getMainExecutor(this))
}
#SuppressLint("MissingSuperCall")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
if(requestCode == Constants.REQUEST_CODE_PERMISSIONS){
if(allPermissionGranted()){
startCamera()
}
else
{
Toast.makeText(this, "Permissions Not Granted by User", Toast.LENGTH_SHORT).show()
finish()
}
}
}
private fun allPermissionGranted() =
Constants.REQUIRED_PERMISSIONS.all{
ContextCompat.checkSelfPermission(
baseContext, it
) == PackageManager.PERMISSION_GRANTED
}
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
}
}
Emirhan.
I believe the error is in "outputOption".
Switch to this code and try to run the application and click the button.
Another thing, when trying to run the program and your app closes you can use the "Debug" to see which line of code is giving problem.
I hope it worked out, hug.
...
val outputFilesOption = ImageCapture.OutputFileOptions.Builder(photoFile).build()
imageCapture.takePicture(
outputFilesOption,getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
...

Android Jetpack Passing Data Between Composables

I'm trying to pass a constantly updating variable "message" across my Jetpack Composables. I have a draggable box that tracks the coordinates of the box but I'm trying to send the real-time data through a TCP connection. However, I noticed that the current coordinate of the draggable box isn't passing through to the other Composable or the socket -only the same value is passed despite message changing continuously due to me dragging the box. Also, the moment dataSendButton() is pressed, the createDragImage() and its draggable box stops animating/running.
var message = "" // global Android send message
class MainActivity : ComponentActivity() {
private var textView: TextView? = null
dataSendButton()
createDragImage()
...
}
}
}
#Composable
fun createDragImage(){
val context = LocalContext.current
...
Box() {
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
Box(
Modifier
.offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
.background(Color.Transparent)
.size(150.dp)
.border(BorderStroke(4.dp, SolidColor(Color.Red)))
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offsetX = someConstantX
offsetY += dragAmount.y
message = offsetY.toString()
...
#Composable
fun dataSendButton() {
val context = LocalContext.current
...
Button(
onClick = {
// **ISSUE: message in this composable is not getting updated with message value from createDragImage()
val b1 = MainActivity.TCPconnector_client(context, message)
b1.execute()
},
{
Text(text = "Send Data", color = Color.White, fontSize = 20.sp)
}
}
}
}
}
It is because that is not how you store state in Compose.
Change the declaration of the variable.
var message by mutableStateOf(...)
Then the changes to it will trigger a recomposition, and so the rest of the code should remain the same. It is always recommended to store the state holders in a viewmodel, and pass the viewmodel around instead.
This is a working code with viewmodel
class MainActivity : ComponentActivity() {
private var textView: TextView? = null
val vm by viewmodels<MViewModel>()
dataSendButton(vm.message, vm:: onMessageChange)
createDragImage(vm.message)
...
}
}
}
#Composable
fun createDragImage(message: String, onMessageChange: (String) -> Unit){
val context = LocalContext.current
...
Box() {
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
Box(
Modifier
.offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
.background(Color.Transparent)
.size(150.dp)
.border(BorderStroke(4.dp, SolidColor(Color.Red)))
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offsetX = someConstantX
offsetY += dragAmount.y
onMessageChange (offsetY.toString())
...
#Composable
fun dataSendButton(message: String) {
val context = LocalContext.current
...
Button(
onClick = {
// **ISSUE: message in this composable is not getting updated with message value from createDragImage() // This seems to be an error. Calling a Composable from onClick?
val b1 = MainActivity.TCPconnector_client(context, message)
b1.execute()
},
{
Text(text = "Send Data", color = Color.White, fontSize = 20.sp)
}
}
}
}
}
class MViewModel: ViewModel(){
var message by mutableStateOf("")
private set //do not allow external modifications to ensure consistency
fun onMessageChange (newMessage: String){
message = newMessage
}
}
Note this is the ideal way of doing such implementation. However, for your specific case, if you do not need to access it anywhere else, only changing the declaration as described in the second line of the answer should do
Thanks

Getting weird Error when trying LazyColumn() in Jetpack compose

I am trying to run a simple LazyColumn Object, but am unable to run it without this weird error.
Here is my code:
#Composable
fun Test(){
LazyColumn() {
Text(text = "Placeholder", fontSize= 30.sp)
Spacer(modifier = Modifier.padding(10.dp))
}
}
Here are the errors:
org.jetbrains.kotlin.diagnostics.SimpleDiagnostic#74c0fa2 (error: could not render message)
org.jetbrains.kotlin.diagnostics.SimpleDiagnostic#c077eec3 (error: could not render message)
Is it something wrong with my code, or is it a bug?
*I wanted to test the scroll function by copy and pasting the lines after the LazyColumn() statement over and over
With 1.0.0-beta04 you can use:
val itemsList = (0..50).toList()
LazyColumn() {
items(itemsList) {
Text(text = "Placeholder", fontSize = 30.sp)
Spacer(modifier = Modifier.padding(10.dp))
}
}
In the LazyListScope in order to display the items you have to use one the provided functions:item, items, itemsindexed and stickyHeader.
The error studio should be showing is #Composable invocations can only happen from the context of a #Composable function; this is the error you get when you would compile this function. That Studio shows (error: could not render message) is a known bug that the team is working on.
The reason the compose compiler plugin generates this error is the lambda expected by LazyColumn is not a composable lambda but is the LazyList DSL in which the column is described. For example, something like,
#Composable
fun Test(){
LazyColumn() {
items(10_000) {
Text(text = "Placeholder $it", fontSize = 30.sp)
Spacer(modifier = Modifier.padding(10.dp))
}
}
}
is probably what you wanted. It doesn't create 10,000 items, it only creates enough to fit on the screen and will create additional rows as needed (discarding rows as they are occluded) up to row 9,999.
Try this:
#Composable
fun Test(){
LazyColumn() {
for (i in 1..10) {
TestItem(i)
}
}
}
#Composable
fun TestItem(i: Int) {
Text(text = "Placeholder $i", fontSize = 30.sp)
Spacer(modifier = Modifier.padding(10.dp))
}

App crashes when I click a Button if EditText is empty

I wrote a simple temperature converter app, everything is working fine except when user leaves EditText blank/null but selects one of the radio buttons, The App crashes.
Here is the Kotlin Code:
class MainActivity : AppCompatActivity() {
lateinit var etTemp: EditText
lateinit var radioGroup: RadioGroup
lateinit var btnConverter :Button
lateinit var tempConverted: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
title = "Zeeshan's Temperature Converter"
etTemp = findViewById(R.id.etTemp)
radioGroup = findViewById(R.id.radioGroup)
btnConverter = findViewById(R.id.btnConverter)
tempConverted = findViewById(R.id.tempConverted)
btnConverter.setOnClickListener {
val id = radioGroup.checkedRadioButtonId
val radioButton = findViewById<RadioButton>(id)
if (radioButton == findViewById(R.id.radioC)){
val temp =etTemp.text.toString().toInt()
val result = temp * 9/5 + 32
tempConverted.setText(result.toString())
}
else if (radioButton == findViewById(R.id.radioF)){
val tempy =etTemp.text.toString().toInt()
val resulty = (tempy - 32) / 1.8
tempConverted.setText(resulty.toString())
}
else{
Toast.makeText(this#MainActivity, "Select one conversion scale", Toast.LENGTH_SHORT).show()
}
}
}
}
You should check if etTemp.text.ToString() == "" (that is empty string) if it is, then do not try to convert it into int. The problem appears when you try to convert "null" value to int.
Inside the listener add a check for for EditText to check whether it is empty or not,
btnConverter.setOnClickListener {
// Add the validation check here ... like this -> if(etTemp.length() > 0){
val id = radioGroup.checkedRadioButtonId
val radioButton = findViewById<RadioButton>(id)
if (radioButton == findViewById(R.id.radioC)){
val temp =etTemp.text.toString().toInt()
val result = temp * 9/5 + 32
tempConverted.setText(result.toString())
}
else if (radioButton == findViewById(R.id.radioF)){
val tempy =etTemp.text.toString().toInt()
val resulty = (tempy - 32) / 1.8
tempConverted.setText(resulty.toString())
}
else{
Toast.makeText(this#MainActivity, "Select one conversion scale", Toast.LENGTH_SHORT).show()
}
// Close the check here -> }
else{
// Prompt the user to put some text in the field - this is called form validation before processing
// Toast.makeText(.....).show
}
}
check if the user has entered some input or not, one way is like below (Kotlin):
if(!etTemp.text.isNullOrEmpty())
{
temp =etTemp.text.toString().toInt()
}

How to define multiple sharedPreferences?

I have managed to get the sharedPreferences saving values. But i don't know how to make it reference the text i am clicking on. In the // Close Alert Window section when i click ok to change the text. Ok dismisses alert dialog, then suppose to add the new price to list in sharedPreferences.
In the putString() if i use putString("Price$it", input.text.toString()).applyit doesn't appear to do anything. However if i use "Price1" any text i change is saved and upon reopening the app Price1is changed to the new price. So i know the method is working. i just have no clue how to save the particular text i am editing. I hope this makes sense. Thanks for your time.
// Created Private Price List
val sharedPreferences = getSharedPreferences("priceList", Context.MODE_PRIVATE)
//Price
(1..912).forEach {
val id = resources.getIdentifier("Price$it", "id", packageName)
val tv = findViewById<TextView>(id)
tv.text = sharedPreferences.getString("Price$it","0.00")
}
(1..912).forEach {
val id = resources.getIdentifier("Price$it", "id", packageName)
val tv = findViewById<TextView>(id)
tv.setOnLongClickListener {
//Alert Window
val alertDialog = AlertDialog.Builder(this#MainActivity).create()
alertDialog.setTitle("NEW PRICE")
val input = EditText(this#MainActivity)
//Alert Submit on Enter
input.setOnKeyListener { v, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
// Input changes text
tv.text = input.text
when {
tv.text.startsWith("-") -> tv.setTextColor(Color.RED)
tv.text.startsWith("+") -> tv.setTextColor(Color.GREEN)
else -> {
tv.text = "_"
tv.setTextColor(Color.DKGRAY)
}
}
// Close Alert Window
alertDialog.dismiss()
// TODO Save Price Table //THIS PART vvv
sharedPreferences.edit().putString("Price1", input.text.toString()).apply()
}
false
}
val lp = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
input.layoutParams = lp
alertDialog.setView(input)
alertDialog.show()
return#setOnLongClickListener true
}
}
You are shadowing it. In your scope you are referencing the argument of tv.setOnLongClickListener. Specify the argument name so it's not shadowed by inner lambdas.
(1..912).forEach { index ->
...
sharedPreferences.edit().putString("Price$index", input.text.toString()).apply()
}

Resources