Related
Please i need help to optimize the input filter on my customEditText
Requirement:
min float = 0.001
max float = 1000.000
decimal number = 3
I want to avoid 0 from the user set but he can enter something like 0.01 , 0.001 , 1.555 and 1000.000
This is what i've tested so far:
I used this function to set the min and max
class EditTextInputFilter(min: Float, max: Float) : InputFilter {
private val min: Float = min.coerceAtMost(max)
private val max: Float = min.coerceAtLeast(max)
override fun filter(source: CharSequence, i: Int, i2: Int, spanned: Spanned, i3: Int, i4: Int): CharSequence? {
try {
val input = (spanned.toString() + source.toString()).toFloatOrZero()
if (isInRange(min, max, input)) {
return null
}
} catch (nfe: NumberFormatException) {
Logger.error(nfe.localizedMessage!!)
}
return ""
}
private fun isInRange(min: Float, max: Float, value: Float): Boolean {
return value in min..max
}
}
I used this Decimal filter to remove comment and un-useful dots
class DecimalFilter(private val decimalDigits: Int) : InputFilter {
override fun filter(source: CharSequence, i: Int, i2: Int, spanned: Spanned, i3: Int, i4: Int): CharSequence? {
var dotPos = -1
val len = spanned.length
for (decimalsI in 0 until len) {
val c = spanned[decimalsI]
if (c == '.' || c == ',') {
dotPos = decimalsI
break
}
}
if (dotPos >= 0) {
// protects against many dots
if (source == "." || source == ",") return ""
// if the text is entered before the dot
if (i4 <= dotPos) return null
if (len - dotPos > decimalDigits) return ""
}
return null
}
}
class CustomEditText : TextInputEditText {
private var decimals = 3
private var min = 0.001f
private var max = 1000f
private lateinit var oldFilters: MutableList<InputFilter>
constructor(context: Context?) : super(context!!) {
init(null)
}
constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) {
init(attrs)
}
constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context!!, attrs, defStyle) {
init(attrs)
}
private fun init(attrs: AttributeSet?) {
isInEditMode
if (attrs != null) {
val a = context.theme.obtainStyledAttributes(attrs, R.styleable.CustomEditText, 0, 0)
decimals = a.getInt(R.styleable.CustomEditText_decimals, decimals)
min = a.getFloat(R.styleable.CustomEditText_min, min)
max = a.getFloat(R.styleable.CustomEditText_max, max)
}
oldFilters = filters.toMutableList()
addFilters()
}
private fun addFilters() {
val inputFilters = ArrayList<InputFilter>(oldFilters.size + 1)
inputFilters.addAll(oldFilters)
inputFilters.add(EditTextInputFilter(min, max))
inputFilters.add(DecimalFilter(decimals))
filters = inputFilters.toTypedArray()
}
fun setMin(min: Float) {
this.min = min
addFilters()
}
fun setMax(max: Float) {
this.max = max
addFilters()
}
fun setDecimals(decimals: Int) {
this.decimals = decimals
addFilters()
}
}
My problem now is, i can not enter 0
The problem is that you're validating the field as the user types and since the range of allowed values is 0.001..1000.000 you cannot type the number 0 (even if the intention is to enter 0.1).
This is not a coding but a specification problem.
The way it's specified is:
don't accept values outside 0.001..1000.000
validate as the user types
It's impossible to meet these requirements. I see a couple of solutions:
validate once the user submits (not as the user types)
validate without blocking the user input (by using setError in a TextWatcher)
allow 0 values in your filter (range 0..1000.000) but add another validation when the user submits to eliminate the 0
allow 0 values in your filter (range 0..1000.000) and disable the submit button (or whatever you have to process the input) if the value isn't in range
From a ux perspective I'd vote for the last option and maybe also indicate to the user what values are allowed so they know why they can't continue
#EmanuelMoecklin After reading your proposal answer i reviewed my code with this code below and it works perfectly and also i can re-use it for other input field.
I use my EditTextInputFilter class to set the min and max. The DecimalFilter class to set the number of decimal
class EditTextInputFilter(min: Float, max: Float) : InputFilter {
private val min: Float = min.coerceAtMost(max)
private val max: Float = min.coerceAtLeast(max)
override fun filter(source: CharSequence, i: Int, i2: Int, spanned: Spanned, i3: Int, i4: Int): CharSequence? {
try {
val input = (spanned.toString() + source.toString()).toFloatOrZero()
if (isInRange(min, max, input)) {
return null
}
} catch (nfe: NumberFormatException) {
Logger.error(nfe.localizedMessage!!)
}
return ""
}
private fun isInRange(min: Float, max: Float, value: Float): Boolean {
return value in min..max
}
}
class DecimalFilter(private val decimalDigits: Int) : InputFilter {
override fun filter(source: CharSequence, i: Int, i2: Int, spanned: Spanned, i3: Int, i4: Int): CharSequence? {
var dotPos = -1
val len = spanned.length
for (decimalsI in 0 until len) {
val c = spanned[decimalsI]
if (c == '.' || c == ',') {
dotPos = decimalsI
break
}
}
if (dotPos >= 0) {
// protects against many dots
if (source == "." || source == ",") return ""
// if the text is entered before the dot
if (i4 <= dotPos) return null
if (len - dotPos > decimalDigits) return ""
}
return null
}
}
I've created another validate function :
private fun validateInput(input: String): Boolean {
val double = input.toDoubleOrNull()
return double != null && double in 0.001f...1000f
}
Than i set the input filter like this on my input field:
binding.valueTiet.filters = arrayOf(EditTextInputFilter(0f, 1000f), DecimalFilter(3))
binding.valueTiet.doAfterTextChanged {
if (validateInput(it)) {
binding.valueTietTil.error = ""
Logger.info("Value accepted")
} else {
binding.valueTietTil.error = "Allowed range [0.001-1000]"
}
}
In my application i have one List and into this List i have one integer value.
I want check post id wit this integer of List, if this id is equal, i should change UI.
I write below codes, but in my codes just change last item UI.
My code:
for (int i = 0; i <= data.getBasket().size() - 1; i++) {
Log.e("BooksIdLog", bookId + " - " + data.getBasket().get(i).getBookId());
if (bookId == data.getBasket().get(i).getBookId()) {
detailPage_bottomSheetPrintVersion.setBackground(context.getResources()
.getDrawable(R.drawable.bg_rounded_stroke_red));
detailPage_bottomSheetPrintVersion.setText(getString(R.string.removeFromBasket));
detailPage_bottomSheetPrintVersion.setTextColor(context.getResources()
.getColor(R.color.red));
detailPage_bottomSheetPrintVersion.setOnClickListener(view ->
presenter.callRemoveBookBasketApi(apiHash, userToken, bookId)
);
} else {
detailPage_bottomSheetPrintVersion.setBgColor(context.getResources()
.getColor(R.color.platinumGray));
detailPage_bottomSheetPrintVersion.setText(getString(R.string.printVersion));
detailPage_bottomSheetPrintVersion.setTextColor(context.getResources()
.getColor(android.R.color.black));
detailPage_bottomSheetPrintVersion.setOnClickListener(view -> {
presenter.callAddBookBasketApi(apiHash, userToken, bookId);
});
}
}
Data fields :
data class Basket(
#SerializedName("basket_id")
val basketId: Int = 0, // 61
#SerializedName("book_id")
val bookId: Int = 0, // 156570
#SerializedName("count")
val count: Int = 0, // 1
#SerializedName("id")
val id: Int = 0, // 156570
#SerializedName("inventory")
val inventory: Int = 0, // 0
#SerializedName("price_of_each_book")
val priceOfEachBook: String = "", // 0
#SerializedName("title")
val title: String = ""
)
For example :
I have 3 item into this List and this 3 items ids is = 155060 - 155070 - 154030 , when go to detail page activity just update UI in this id 154030 not all of ids !
How can i fix it?
If you just want to use forEach instead of a classic for-loop for a MutableList<Basket> (for example), then you can do it in Kotlin like this:
data.getBasket().forEach(basket -> {
Log.e("BooksIdLog", bookId + " - " + basket.getBookId());
if (bookId == basket.getBookId()) {
...
} else {
...
}
}
But I think your loop is working correctly...
My guess is you are not properly updating the value of bookId which is used for comparison. Check where it is declared, defined and used...
I have received multiple comma-separated string integers as input, for example, the following strings :
"5,6,0"
"0,1,2"
"1,2,3,4"
Each of these integers is meant to represent a day of the week
0 = Sunday 1 = Monday 2 = Tuesday 3 = Wednesday 4 = Thursday 5 =
Friday 6 = Saturday
In the case of the first string, it would mean that Thursday to Sunday
The second string would be valid from Sunday to Tuesday
The third-string would be valid from Monday to Thursday
Currently, I am using the following
private fun mapOfDays(validDays: String): LinkedHashMap<Int, String>
{
if (!validDays.isBlank())
{
val daysArray = validDays.split("\\s*,\\s*") as Array<String>
var mapDays = LinkedHashMap<Int, String>()
var mapDay = LinkedHashMap<Int, String>()
mapDays[0] = "SUNDAY"
mapDays[1] = "MONDAY"
mapDays[2] = "TUESDAY"
mapDays[3] = "WEDNESDAY"
mapDays[4] = "THURSDAY"
mapDays[5] = "FRIDAY"
mapDays[6] = "SATURDAY"
for (day in daysArray)
{
if (mapDays.containsKey(day.toInt()))
{
mapDay[day.toInt()] = mapDays[day.toInt()]!!
}
}
return mapDay
}
return LinkedHashMap()
}
private fun mappedDays(mapOfDays: LinkedHashMap<Int, String>?): String
{
if (!mapOfDays.isNullOrEmpty())
{
val mapSize = mapOfDays.size
if (mapSize > 6) return "All Day"
if (mapSize > 5) return sixDayString(mapOfDays)
if (mapSize > 4) return fiveDayString(mapOfDays)
if (mapSize > 3) return fourDayString(mapOfDays)
if (mapSize > 2) return threeDayString(mapOfDays)
if (mapSize > 1) return twoDayString(mapOfDays)
if (mapSize > 0) return oneDayString(mapOfDays)
}
return ""
}
private fun twoDayString(mapOfDays: LinkedHashMap<Int, String>): String
{
val firstPosition: Int = mapOfDays.keys.toIntArray()[0]
val lastPosition: Int = mapOfDays.keys.toIntArray()[1]
val lastDay = Days.values()[lastPosition]
val firstDay = Days.values()[firstPosition]
return "$firstDay and $lastDay"
}
private fun oneDayString(mapOfDays: LinkedHashMap<Int, String>): String
{
var firstPosition: Int = mapOfDays.keys.toIntArray()[0]
val firstDay = Days.values()[firstPosition]
return "$firstDay"
}
private fun threeDayString(mapOfDays: LinkedHashMap<Int, String>): String
{
val firstPosition: Int = mapOfDays.keys.toIntArray()[0]
val secondPosition: Int = mapOfDays.keys.toIntArray()[1]
val thirdPosition: Int = mapOfDays.keys.toIntArray()[2]
val firstDay = Days.values()[firstPosition]
val secondDay = Days.values()[secondPosition]
val lastDay = Days.values()[thirdPosition]
return "$firstDay, $secondDay and $lastDay"
}
private fun fourDayString(mapOfDays: LinkedHashMap<Int, String>): String
{
val firstPosition: Int = mapOfDays.keys.toIntArray()[0]
val secondPosition: Int = mapOfDays.keys.toIntArray()[1]
val thirdPosition: Int = mapOfDays.keys.toIntArray()[2]
val fourthPosition: Int = mapOfDays.keys.toIntArray()[3]
val firstDay = Days.values()[firstPosition]
val secondDay = Days.values()[secondPosition]
val thirdDay = Days.values()[thirdPosition]
val lastDay = Days.values()[fourthPosition]
return "$firstDay, $secondDay, $thirdDay and $lastDay"
}
private fun fiveDayString(mapOfDays: LinkedHashMap<Int, String>): String
{
val firstPosition: Int = mapOfDays.keys.toIntArray()[0]
val secondPosition: Int = mapOfDays.keys.toIntArray()[1]
val thirdPosition: Int = mapOfDays.keys.toIntArray()[2]
val fourthPosition: Int = mapOfDays.keys.toIntArray()[3]
val fifthPosition: Int = mapOfDays.keys.toIntArray()[4]
val firstDay = Days.values()[firstPosition]
val secondDay = Days.values()[secondPosition]
val thirdDay = Days.values()[thirdPosition]
val fourthDay = Days.values()[fourthPosition]
val lastDay = Days.values()[fifthPosition]
return "$firstDay, $secondDay, $thirdDay, $fourthDay and $lastDay"
}
private fun sixDayString(mapOfDays: LinkedHashMap<Int, String>): String
{
var firstPosition: Int = mapOfDays.keys.toIntArray()[0]
var lastPosition: Int = 0
for (day in mapOfDays.keys)
{
lastPosition = day
}
val lastDay = Days.values()[lastPosition]
val firstDay = Days.values()[firstPosition]
return "$firstDay to $lastDay"
}
}
enum class Days()
{
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}
However, my current implementation is on able to tell me which days are included and not able to map out the group of days for example:
If I got "0,1,3,4,5,6" The final string output that I would want to have is the following: Wednesday to Monday
or
"0,1,3,4,5" would lead to the following result: Sunday, Monday, Wednesday to Friday.
package days
import java.lang.IllegalArgumentException
class DaysFactory {
fun dayFromInt(index: Int): Day {
return when (index) {
0 -> Day.Sunday
1 -> Day.Monday
2 -> Day.Tuesday
3 -> Day.Wednesday
4 -> Day.Thursday
5 -> Day.Friday
6 -> Day.Saturday
else -> throw IllegalArgumentException("illigal index :$index")
}
}
enum class Day(val index: Int) {
Sunday(0), Monday(1), Tuesday(2), Wednesday(3), Thursday(4), Friday(5), Saturday(6)
}
}
class DaysRange(val seed: String) {
var stringFormat = ""
private fun getTomorrow(dayIndex: Int): Int {
if (dayIndex != 6) return dayIndex + 1
return 0
}
override fun toString(): String =stringFormat
init {
if (isValidInput(seed)) {
val dayFactory = DaysFactory()
val indexes = seed.split(",").map { it.toInt() }
val days = indexes.map { dayFactory.dayFromInt(it) }
val ranges = splitIndexesToRanges(indexes)
ranges.forEach { range ->
if (range.size > 2) {
stringFormat += "${dayFactory.dayFromInt(range.first())} to ${dayFactory.dayFromInt(range.last())},"
} else
range.forEach {
stringFormat += "${dayFactory.dayFromInt(it)},"
}
}
stringFormat = stringFormat.dropLast(1)
}
}
private fun splitIndexesToRanges(daysRange: List<Int>): ArrayList<List<Int>> {
val result = ArrayList<List<Int>>()
val slicePoint = ArrayList<Int>()
for (i in 0 until daysRange.size - 1) {
if (getTomorrow(daysRange[i]) != daysRange[i + 1]) {
slicePoint.add(i)
}
}
var start = 0
slicePoint.forEach {
result.add(daysRange.slice(start..it))
start = it + 1
}
result.add(daysRange.slice(start until daysRange.size))
return result
}
}
private fun isValidInput(seed: String): Boolean = true
fun main(args: Array<String>) {
val input = listOf(
"0,1,2,4,5,6",
"5,6,0",
"1,2,3,4"
)
input.forEach {
val dr = DaysRange(it)
println(dr)
}
}
example output:
Sunday to Tuesday,Thursday to Saturday
Friday to Sunday
Monday to Thursday
If you can, I would just stick to a given time API (e.g. java.time if you are on Java 8 or joda-time, etc.). The following solution would also work with your enum, but you need to adapt it a bit (i.e. DayOfWeek has getDisplayName and also allows to add single days and always get the next consecutive day).
I split the work into 3 separate tasks.
reading the input into a list of DayOfWeek:
fun readInput(input : String) : List<DayOfWeek> = input.splitToSequence(",")
.map(String::toInt)
.map {
/* your 0 is Sunday which is 7 for DayOfWeek; rest is the same */
if (it == 0) 7 else it
}
.map(DayOfWeek::of)
.toList()
Maybe you want to add .distinct().sorted() to it or want to validate the input beforehand... That depends on what you really want to ensure...
transforming the day of weeks into a list of consecutive days:
fun List<DayOfWeek>.toDayRangeList() : List<DayRange> = fold(mutableListOf<DayRange>()) { consecutiveDaysList, day ->
consecutiveDaysList.apply {
lastOrNull()?.takeIf { it.to + 1 == day }?.apply {
to = day
} ?: add(DayRange(day))
}
}
For this I also introduced a DateRange-class in order to easily mutate the ending date... You can do this also with immutable objects, but I found this way easier. The DateRange also includes some helper methods to easily get the actual date in the form you want (in my example FULL_STANDALONE):
data class DayRange(var from: DayOfWeek, var to: DayOfWeek = from) {
private fun DayOfWeek.toFullString(locale : Locale) = getDisplayName(TextStyle.FULL_STANDALONE, locale)
fun toString(locale : Locale) : String = when (from) {
// TODO add missing locale specific strings!
to -> from.toFullString(locale)
to + 1 -> "All day"
else -> "${from.toFullString(locale)} to ${to.toFullString(locale)}"
}
// just for convenience we use our custom toString-function:
override fun toString() = toString(Locale.getDefault())
}
optionally "flatten" the list, i.e. if the last day and the first are consecutive, merge them into a single range. As we are dealing with DayOfWeek directly we can simply just add another day and compare two days, regardless whether one is the last day of the week or not:
fun List<DayRange>.flatten(): List<DayRange> {
if (size > 1) {
val first = first()
val last = last()
if (last.to + 1 == first.from)
return dropLast(1).drop(1)
.toMutableList()
.apply {
add(DayRange(last.from, first.to))
}
}
return this
}
Putting it all together / demo:
listOf("1", "1,2", "1,0", "1,2,3", "1,2,4,5", "1,2,4,5,0", "1,2,3,4,5,6,0", "2,3,4,5,6,0,1")
.forEach { input ->
print(input)
readInput(input)
.toDayRangeList()
.flatten()
.joinToString(", ")
.also {
println("-> $it")
}
}
which prints the following:
1 -> Monday
1,2 -> Monday to Tuesday
1,0 -> Sunday to Monday
1,2,3 -> Monday to Wednesday
1,2,4,5 -> Monday to Tuesday, Thursday to Friday
1,2,4,5,0 -> Thursday to Friday, Sunday to Tuesday
1,2,3,4,5,6,0 -> All day
2,3,4,5,6,0,1 -> All day
I am working on app where an edittext is having $0 as pre-defined text.
Zero is editable. But as soon as user enters an amount like $80, its showing $080.
I am using textwatcher to non deleted $ and replaced at time of printing value.
How can i achieve the output as $80 when user is typing an amount
preset value - $0
after typing $80,
Output = $080
Expected = $80
Thanks in advance.
amountEdittext.setText("$0");
Selection.setSelection(amountEdittext.getText(),
amountEdittext.getText().length());
amountEdittext.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
#Override
public void afterTextChanged(Editable s) {
if (!s.toString().startsWith("$")) {
amountEdittext.setText("$");
Selection.setSelection(amountEdittext.getText(), amountEdittext.getText().length());
}
}
});
This should solve your question.
amountEdittext.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
if (editable.length() > 2) {
for (int i = 0; i < editable.length() - 1; i++) {
if (editable.charAt(i) == '$' && editable.charAt(i + 1) == '0') {
editable.delete(i + 1, i + 2);
}
}
}
}
});
}
Better use a InputFilter than using TextWatcher. Since you've the $ sign better to remove it from the EditText and use one TextView along with EditText horizontally. So that you can get rid of the dollar sign
class DecimalDigitsInputFilter(digitsBeforeZero: Int, digitsAfterZero: Int) : InputFilter {
private val mPattern: Pattern
init {
mPattern =
Pattern.compile("[0-9]{0," + (digitsBeforeZero ) + "}+((\\.[0-9]{0," + (digitsAfterZero ) + "})?)||(\\.)?")
}
override fun filter(
source: CharSequence?,
start: Int,
end: Int,
dest: Spanned?,
dstart: Int,
dend: Int
): CharSequence? {
if (source != null && dest != null) {
val replacement = source.subSequence(start, end).toString()
val newVal = (dest.subSequence(0, dstart).toString() + replacement
+ dest.subSequence(dend, dest.length).toString())
val matcher = mPattern.matcher(newVal)
if (matcher.matches())
return null
return if (TextUtils.isEmpty(source))
dest.subSequence(dstart, dend)
else
return ""
}
return ""
}
}
I have just realized what you are doing, Selection.setSelection moves the cursor to the end of text in the edittext,so that will be after zero.
So, here is a solution that i think would work;
amountEdittext.setText("$0");
amountEdittext.setOnFocusChange(View view, boolean b) {
if (b && amountEdittext.getText().toString().matches("^\\$0$")) {
amountEdittext.setText("$");
Selection.setSelection(amountEdittext.getText(), amountEdittext.getText().length());
} else {
Selection.setSelection(amountEdittext.getText(), amountEdittext.getText().length());
}
}
Instead of using the text property you should HINT
amountEdittext.setHint("$0");
you will no longer require any other code.
and keep the text blank you can then add validation to check for empty text.
you can change the hint when it has focus using setOnFocusChangeListener()
I have found a solution to my problem here Create new column with function in Spark Dataframe
But i am having difficulty in converting the below code to Java since it's in Scala
import org.apache.spark.sql.functions._
val myDF = sqlContext.parquetFile("hdfs:/to/my/file.parquet")
val coder: (Int => String) = (arg: Int) => {if (arg < 100) "little" else "big"}
val sqlfunc = udf(coder)
myDF.withColumn("Code", sqlfunc(col("Amt")))
Can someone provide me the Java equivalent code for this?. I am stuck in converting below 2 lines
val coder: (Int => String) = (arg: Int) => {if (arg < 100) "little" else "big"}
val sqlfunc = udf(coder)
Thanks,
Create your User Defined Function:
public class CodeUdf implements UDF1<Integer, String>{
#Override
public String call(Integer integer) throws Exception {
if(integer < 100)
return "little";
else
return"big";
}
}
Tell Spark about it
sqlContext.udf().register("Code", new CodeUdf(), DataTypes.IntegerType);
Use it in a select.
df.selectExpr("value", "Code(value)").show();
import org.apache.spark.sql.functions._
val myDF = sqlContext.parquetFile("hdfs:/to/my/file.parquet")
//val coder: (Int => String) = (arg: Int) => {if (arg < 100) "little" else "big"}
//val sqlfunc = udf(coder)
myDF.selectExpr("Code", "case when Amt < 100 'little' else 'big' end ")