How to restrict input length using two-way data binding with live data the correct way? - android-layout

I am building an app where users have to input some information. For that reason, I am using TextInputLayout
and TextInputEditText to have more interaction with them. For instance, when the user input more than 5 characters I would like to show an error message etc.
At the moment I am able to do the input restriction, however, most of the code is in the Activity and that is not what I like to have. Also, I have to assign the TextInputEditText in the activity to set a new string back to that field layout so I do not think that this is correct two-way data binding.
My main goal here is to restrict the user to input more than 5 characters and more importantly keep all the code inside the ViewModel.
Screen:
...
...
Layout:
...
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/til_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:gravity="center"
android:hint="#string/hint_order"
tools:hint="#string/hint_order"
app:errorEnabled="true"
app:errorTextColor="#color/red_currant_red"
app:passwordToggleEnabled="true"
app:passwordToggleTint="#color/row_text_brown"
app:boxBackgroundColor="#color/white"
app:counterEnabled="true"
app:counterMaxLength="5"
android:minHeight="20dp"
app:expandedHintEnabled="false"
app:hintAnimationEnabled="false"
style="#style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
android:layout_column="1">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/tiet_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:selectAllOnFocus="true"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#color/black_text"
android:textColorHighlight="#color/blue_onahau"
android:text="#={ myNewViewModel._inputWordOrderTextInt}"
android:inputType="textPassword"/>
</com.google.android.material.textfield.TextInputLayout>
...
ViewModel:
...
val _inputWordOrderTextInt = MutableLiveData<String>()
val inputWordOrderTextInt: LiveData<String>
get() = _inputWordOrderTextInt
...
Activity:
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityNewWordBinding.inflate(layoutInflater)
binding.myNewViewModel = newWordViewModel
val view = binding.root
setContentView(view)
manageInputTypeForWordOrderTextInt()
}
...
private fun manageInputTypeForWordOrderTextInt() {
newWordViewModel.inputWordOrderTextInt.observe(this, Observer { input ->
if (input != null && input.isNotEmpty()) {
if (input.length > 5) {
val newInput = input.substring(0, 5)
binding.tiet2.setText(newInput)
binding.tiet2.setSelection(newInput.length)
}
}
})
}
...

Related

Unable to Bring Image Captured (using camera) In Front of an ImageButton in Kotlin Android Studio

I am developing a simple form in Kotlin Android Studio that includes taking a photo using the camera. After taking a photo using the camera, I want the image to be set in the camera button (which is an ImageButton).
Currently, it looks like this,
Current Result
How do I send the image to front?
I used a bringToFront() method (shown in the code snippet below) but to no avail.
Here are the relevant codes:
class FormActivity : AppCompatActivity() {
lateinit var currentPhotoPath: String
val REQUEST_IMAGE_CAPTURE = 1
var selectedPhotoUri : Uri? = null
companion object {
const val REQUEST_FROM_CAMERA = 1001
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_formactivity)
...
val imageButton = findViewById<ImageButton>(R.id.imageButton)
// to launch the camera
imageButton.setOnClickListener {
takePictureUsingCamera()
}
}
private fun takePictureUsingCamera(){
ImagePicker.with(this).cameraOnly()
.crop()
.start(REQUEST_FROM_CAMERA)
}
// to access the image captured
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == RESULT_OK) {
when (requestCode){
REQUEST_FROM_CAMERA -> {
selectedPhotoUri = data!!.data
val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, selectedPhotoUri)
val bitmapDrawable = BitmapDrawable(bitmap)
val imageButton = findViewById<ImageButton>(R.id.imageButton)
// add the image to the image button
imageButton.setImageDrawable(bitmapDrawable)
imageButton.bringToFront()
}
}
}
}
}
Here is my XML code:
<ImageButton
android:id="#+id/imageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="#+id/submitButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="#+id/textView"
app:layout_constraintTop_toBottomOf="#+id/spinnerRslt"
android:adjustViewBounds="true"
android:scaleType="fitXY"
app:srcCompat="#mipmap/ic_camera_capture" />
Before: ImageButton - before clicking/taking a photo
After: After the image has been taken

Android Studio Room data without livedata

I got the following Room database and want to output the name of a random user in a textview. Unfortunately running the code yields the output: kotlin.unit inside the textview. My files look like that:
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var mNameViewModel: NameViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mNameViewModel = ViewModelProvider(this).get(NameViewModel::class.java)
val btn = findViewById<Button>(R.id.btn_addName)
val tv = findViewById<TextView>(R.id.tv_showName)
btn.setOnClickListener {
val text = findViewById<EditText>(R.id.et_enterName)?.text.toString()
val name = Name(0, text)
// Add Data to Database
mNameViewModel.addName(name)
Toast.makeText(applicationContext, "Successfully added $text.", Toast.LENGTH_LONG).show()
val randomName = mNameViewModel.getRandomName()
// Without .toString() I get an error, with it it displays kotlin.unit
tv.text = randomName.toString()
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/tv_showName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.133" />
<EditText
android:id="#+id/et_enterName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:hint="Name"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.244" />
<Button
android:id="#+id/btn_addName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.379" />
</androidx.constraintlayout.widget.ConstraintLayout>
Name.kt
#Entity(tableName = "name_data")
data class Name (
#PrimaryKey(autoGenerate = true) val id: Int,
#ColumnInfo(name = "name") val name: String
)
NameDao.kt
#Dao
interface NameDao {
#Insert
fun addName(name: Name)
#Query("SELECT name FROM name_data ORDER BY RANDOM() LIMIT 1")
fun getRandomName(): String
}
NameDatabase.kt
#Database(entities = [Name::class], version = 1, exportSchema = false)
abstract class NameDatabase: RoomDatabase() {
abstract fun nameDao(): NameDao
companion object{
#Volatile
private var INSTANCE: NameDatabase? = null
fun getDatabase(context: Context): NameDatabase{
val tempInstance = INSTANCE
if(tempInstance != null){
return tempInstance
}
synchronized(this){
val instance = databaseBuilder(
context.applicationContext,
NameDatabase::class.java,
"name_data"
).build()
INSTANCE = instance
return instance
}
}
}
}
NameRepository.kt
class NameRepository(private val nameDao: NameDao) {
fun getRandomName() { nameDao.getRandomName() }
fun addName(name: Name) { nameDao.addName(name) }
}
NameViewModel.kt
class NameViewModel(application: Application): AndroidViewModel(application) {
private val repository: NameRepository
init {
val nameDao = NameDatabase.getDatabase(application).nameDao()
repository = NameRepository(nameDao)
}
fun addName(name: Name) {
viewModelScope.launch(Dispatchers.IO){
repository.addName(name)
}
}
fun getRandomName() {
viewModelScope.launch(Dispatchers.IO){
repository.getRandomName()
}
}
}
This is how the output of textview when pressing the button.
The database gets populated though.
Apreciate any help to get the data displayed. Thank you!
The issue is that in your getRandomNumber method inside viewmodel you don't return anything that's why you get kotlin.unit . You should instead make sure to return a value
A possible solution would be the following.
Create a method inside your dao which takes a number and returns that row (this will be essentially the random name)
From your repo call that method with a random number you could use Random.getNextInt not sure how do you get a random
From your view model call that method
From your button onClick call the viewModel method
Make sure to use suspend where applicable in order to get a result. For the case that I showcased above that would be to launch a coroutine in view level and make the rest of the calls (vm,repo,dao) suspend
So after some testing and back and forth this is what I have and it seems to be working.
If anyone has some things to bring in I gladly accept them and a big thanks to georkost for all the usefull tips!
// No changes made to database
#Entity(tableName = "name_data")
data class Name (
#PrimaryKey(autoGenerate = true) val id: Int,
#ColumnInfo(name = "name") val name: String
)
//*********************************************************************************
// Changed return type from Query to LiveData<String> (was just String before)
#Dao
interface NameDao {
#Insert
fun addName(name: Name)
#Query("SELECT name FROM name_data ORDER BY RANDOM() LIMIT 1")
fun getRandomName(): LiveData<String> // HERE
}
//*********************************************************************************
// Changed getRandomMeal from fun to val
class NameRepository(private val nameDao: NameDao) {
val getRandomMeal: LiveData<String> = nameDao.getRandomName() // HERE
fun addName(name: Name) { nameDao.addName(name) }
}
//*********************************************************************************
// Added getRandomName val, initialized it and removed the fun
class NameViewModel(application: Application): AndroidViewModel(application) {
val getRandomName: LiveData<String> // HERE
private val repository: NameRepository
init {
val nameDao = NameDatabase.getDatabase(application).nameDao()
repository = NameRepository(nameDao)
getRandomName = repository.getRandomMeal // HERE
}
fun addName(name: Name) {
viewModelScope.launch(Dispatchers.IO){
repository.addName(name)
}
}
}
//*********************************************************************************
// Changed the last row to observe the LiveData and display it in the Text View
class MainActivity : AppCompatActivity() {
private lateinit var mNameViewModel: NameViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mNameViewModel = ViewModelProvider(this).get(NameViewModel::class.java)
val btn = findViewById<Button>(R.id.btn_addName)
val tv = findViewById<TextView>(R.id.tv_showName)
btn.setOnClickListener {
val text = findViewById<EditText>(R.id.et_enterName)?.text.toString()
val name = Name(0, text)
// Add Data to Database
mNameViewModel.addName(name)
Toast.makeText(applicationContext, "Successfully added $text.", Toast.LENGTH_LONG).show()
// HERE
mNameViewModel.getRandomName.observe(this, Observer { String -> tv.text = String })
}
}
}

Error inflating class com.inmobi.ads.InMobiBanner

I followed the InMobi guidelines for Android apps step by step but got the following exception from
DataBindingUtil.setContentView(this, R.layout.mainActivity):
Binary XML file line #670: Binary XML file line #670: Error inflating
class com.inmobi.ads.InMobiBanner
<LinearLayout
xmlns:inmobi="http://schemas.android.com/apk/lib/com.inmobi.ads"
android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
android:orientation="vertical">
<TextView android:text="Ad"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.inmobi.ads.InMobiBanner
android:id="#+id/bannerAdInMobi"
android:layout_width="320dp"
android:layout_height="50dp"
inmobi:placementId="plid-1526902340491"
inmobi:refreshInterval="60" />
</LinearLayout>
Could anyone offer a tip on how to diagnose it?
Since no one answered after a month, let me post mine.
InMobil's support has kindly helped me find the culprit - layout inflation before InMobiSdk.init(). An app needs to run InMobiSdk.init() before inflating the layout.
My app cannot do this because it uses InMobil ads only under certain conditions (e.g. only the FREE version has ads), so I have removed com.inmobi.ads.InMobiBanner from the layout, and add it in Java code when it is needed.
Ok I Solved this problem . just follow these steps ->
Step 1: Declare the variable in your Activity/fragment
private lateinit var bannerAd: InMobiBanner
Step 2: Initialite InMobiSDK in onCreate()
override fun onCreate(savedInstanceState: Bundle?) {
bannerAd= Helper.InitializInMobiAds(requireContext())
super.onCreate(savedInstanceState)
}
Step 3: here is InitializInMobiAds() method in Helper Class
fun InitializInMobiAds(context: Context): InMobiBanner{
val consentObject = JSONObject()
try {
// Provide correct consent value to sdk which is obtained by User
consentObject.put(InMobiSdk.IM_GDPR_CONSENT_AVAILABLE, false)
// Provide 0 if GDPR is not applicable and 1 if applicable
consentObject.put("gdpr", "0")
// Provide user consent in IAB format
// consentObject.put(InMobiSdk.IM_GDPR_CONSENT_IAB, “ << consent in IAB format >> ”)
} catch (e: JSONException) {
e.printStackTrace()
}
InMobiSdk.init(context, "AcountIdHere" , consentObject, SdkInitializationListener() {
#Override
fun onInitializationComplete(error : Error?) {
if (null != error) {
Log.e("", "InMobi Init failed -" + error.message.toString())
} else {
Log.d("", "InMobi Init Successful")
}
}
})
return InMobiBanner(context, PLID_here)
}
Step 4: now use .load() method in onCreateView() or in whichever block which runs after onCreate()
binding.adView.load()

cannot find symbol variable imageView2

I am just starting with Android Studio. I got some code from the web on how to ass a splash screen to my app, but it has compiling errors. I could use some help.
the errors are:
error. can not find symbol variable imageView2
error. can not find symbol variable s_img
error. can not find symbol variable s_image_black
error. can not find symbol variable s_image_black
I know this is due to my lack of knowledge, but I am just starting and trying to use this example to learn. Any help would be much appreciated.
Cheers
Paul
package org.quaestio.kotlinconvertedwebview;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.widget.ImageView;
import org.quaestio.kotlinconvertedwebview.MainActivity;
import java.util.Random;
public class Splashscreen extends Activity {
Thread splashTread;
ImageView imageView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splashscreen);
imageView = (ImageView)findViewById(R.id.imageView2);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
int[] ids = new int[]{R.drawable.s_img,R.drawable.s_image_black, R.drawable.s_image_black2};
Random randomGenerator = new Random();
int r= randomGenerator.nextInt(ids.length);
this.imageView.setImageDrawable(getResources().getDrawable(ids[r]));
splashTread = new Thread() {
#Override
public void run() {
try {
int waited = 0;
// Splash screen pause time
while (waited < 3500) {
sleep(100);
waited += 100;
}
Intent intent = new Intent(Splashscreen.this,
MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(intent);
Splashscreen.this.finish();
} catch (InterruptedException e) {
// do nothing
} finally {
Splashscreen.this.finish();
}
}
};
splashTread.start();
}
}
activity_splashscreen below
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#feffc3"
android:layout_gravity="center"
android:id="#+id/lin_lay"
android:gravity="center"
android:orientation="vertical" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/splash"
android:background="#drawable/splash_img" />
</LinearLayout>
The problem is that you're setting an id wich don't correspond with the xml file. For example imageView = (ImageView)findViewById(R.id.imageView2); you need to change imageView2 with your xml imageview id wich is "splash". For the pthers error you need to check that files in the drawable folder.
Hope it was helpful
The ImageView in your xml has id splash, so in your code, instead of:
imageView = (ImageView)findViewById(R.id.imageView2);
you should do
imageView = (ImageView)findViewById(R.id.splash);
Also, this line:
int[] ids = new int[]{R.drawable.s_img, R.drawable.s_image_black, R.drawable.s_image_black2};
assumes that in your drawable folder there exist all these drawables:
s_img, s_image_black, s_image_black2
but apparently they don't, so you have to copy or create them and put them in the drawable folder.

kotlin tcp socket client not working python server

I want to make an app that will connect to my python server using sockets.
When I press the connect button it does not even print the got connection on my PS, please help. Thank you
I have this basic code in kotlin:
//Kotlin Code
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Toast
import java.io.DataOutputStream
import java.net.Socket
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.start_page)
fun connect(v : View) {
try{
val soc = Socket("192.168.1.5", 1419)
val dout = DataOutputStream(soc.getOutputStream())
dout.writeUTF("1")
dout.flush()
dout.close()
soc.close()
}
catch (e:Exception){
e.printStackTrace()
}
}
}
The connect function is activated when clicked on a button, this is the xml code for my start screen
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="#+id/connect"
android:layout_width="108dp"
android:layout_height="50dp"
android:layout_marginBottom="127dp"
android:layout_marginEnd="228dp"
android:layout_marginStart="256dp"
android:onClick="connect"
android:text="#string/connect"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
and this is my code in python server:
#Python code
import socket
s = socket.socket()
s.bind(('0.0.0.0', 1419))
s.listen(5)
c, addr = s.accept()
print ('Got connection from', addr)
code_encoded = c.recv(1024)
code_decoded = code_encoded.decode('utf-8')
print(code_decoded)
c.close()
s.close()
I fixed it by implementing asynctask in my function and used java instead of kotlin but it should work similarly in kotlin as well.
The function is now, like this,
class ServerConnection extends AsyncTask<MainActivity.ConnParams, Void, Void> {
#Override
protected Void doInBackground(MainActivity.ConnParams... params)
{
String ip = params[0].ip;
int port = params[0].port;
String message = params[0].message;
try
{
Socket socket = new Socket(ip, port);
PrintWriter printWriter = new PrintWriter(socket.getOutputStream());
printWriter.write(message);
printWriter.flush();
printWriter.close();
socket.close();
} catch (IOException e){
e.printStackTrace();
}
return null;
}
}
Although asynctask is not good for tasks where you need it to be in background for longer periods of time, at which time I would recommend using android services.

Resources