Custom Lint does not highlight in Android Studio - android

I have my custom lint, that checks XML files for hex colors usage.
It works perfectly when I run
./gradlew myapp:lint
But it does not hightlight problem places. Even more - Analyze -> Inspect Code does not give me result and returns only
No suspicious code found.
Source Code:
class XmlColorsDetector : ResourceXmlDetector() {
companion object {
val ISSUE: Issue = Issue.create(
id = "CustomColorsXml",
briefDescription = "Custom colors detector",
explanation = "Use only theme colors, in other case our themes behaviour will not work properly",
category = Category.CORRECTNESS,
priority = 6,
severity = Severity.ERROR,
implementation = Implementation(
XmlColorsDetector::class.java,
Scope.RESOURCE_FILE_SCOPE
)
)
}
override fun getApplicableAttributes(): Collection<String>? {
return listOf("color", "textColor", "background")
}
override fun getApplicableElements(): Collection<String>? {
return listOf("color")
}
override fun visitAttribute(context: XmlContext, attribute: Attr) {
if (attribute.textContent.startsWith("#") && !context.file.path.containsExcludes()) {
println("uri" + context.file.path)
context.report(ISSUE, context.getLocation(attribute), "You can not use hardcoded colors")
}
}
private fun String.containsExcludes(): Boolean {
val excludes = listOf("/color", "/drawable")
excludes.forEach {
if (this.contains(it))
return true
}
return false
}
}
I tried LintFix but it nothing changed.
How can I achieve a proper hightlight in Android Studio?

Related

Kotlin: Idiomatic way to check for multiple boolean functions

I am trying to validate a form by calling the same functions multiple times. My goal is that the isFormValid function should "wait" till all functions are called and then return the boolean.
My current solution works but it looks really od. Ain't there a better way?
FormValidator Class
class FormValidator(private val context: Context) {
// some strings
private fun String.isValidEmail() = android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()
fun validateNormalET(editText: MutableLiveData<String>, editTextEM: MutableLiveData<String>): Boolean {
if (editText.value.isNullOrEmpty()) {
editTextEM.value = emptyFieldError
return false
}
return true
}
fun validateMinLengthET(editText: MutableLiveData<String>, editTextEM: MutableLiveData<String>, minLength: Int): Boolean {
val errorMessage = when {
minLength < 5 -> postCodeTooFewChar
minLength < 7 -> btNumberTooFewChar
else -> "Error"
}
if (editText.value.isNullOrEmpty()) {
editTextEM.value = emptyFieldError
return false
} else if (editText.value.toString().length < minLength) {
editTextEM.value = errorMessage
return false
}
return true
}
fun validateEmail(editText: MutableLiveData<String>, editTextEM: MutableLiveData<String>): Boolean {
if (editText.value.isNullOrEmpty()) {
editTextEM.value = emptyFieldError
return false
} else if (!editText.value.toString().isValidEmail()) {
editTextEM.value = emailNotValidError
return false
}
return true
}
Current isFormValid Function
fun isFormValid(): Boolean =
formValidator.validateMinLengthET(btNumber, btNumberEM, 7) and
formValidator.validateNormalET(etFirstName, etFirstNameEM) and
formValidator.validateNormalET(etLastName, etLastNameEM) and
formValidator.validateEmail(etEmail, etEmailEM) and
formValidator.validateMinLengthET(etPostCode, etPostCodeEM, 5) and
formValidator.validateNormalET(etCity, etCityEM) and
formValidator.validateNormalET(etStreet, etStreetEM) and
formValidator.validateNormalET(etHouseNumber, etHouseNumberEM)
I appreciate every help, thank you. If there was already a question like this, then I am sorry that I opened another one..
You could use a list with its .all implementation:
fun isFormValid(): Boolean = listOf(
formValidator.validateMinLengthET(btNumber, btNumberEM, 7),
formValidator.validateNormalET(etFirstName, etFirstNameEM),
formValidator.validateNormalET(etLastName, etLastNameEM),
formValidator.validateEmail(etEmail, etEmailEM),
formValidator.validateMinLengthET(etPostCode, etPostCodeEM, 5),
formValidator.validateNormalET(etCity, etCityEM),
formValidator.validateNormalET(etStreet, etStreetEM),
formValidator.validateNormalET(etHouseNumber, etHouseNumberEM)
)
.all { it }
That way you're following the Open/Closed Principle.
If you want to make it slightly shorter, use with(formValidator) {}-scope like #iknow posted in the comment.
EDIT:
If you want it to use as little resources as possible, you could convert the list type to a boolean producer: () -> Boolean
fun isFormValid(): Boolean = listOf(
{ formValidator.validateMinLengthET(btNumber, btNumberEM, 7) },
{ formValidator.validateNormalET(etFirstName, etFirstNameEM) },
...
)
.all { it() }

How to switch between light and dark theme dynamically in app using composables

How would you dynamically switch between theme's color palette with a press of a button inside the app
This is what I am doing so far, but only works when I switch the Android Theme to dark or light mode
AppTheme.Kt
#Model
object ThemeState {
var isLight: Boolean = true
}
#Composable
fun MyAppTheme(
children: #Composable() () -> Unit
) {
MaterialTheme(colors = if (ThemeState.isLight) themeColorsLight else themColorDark) {
children()
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme(children = {
Surface {
Greetings(name = "Android")
}
})
}
}
}
#Composable
fun Greetings(name: String) {
Column(modifier = Modifier.fillMaxHeight()) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = "Hello $name", modifier = Modifier.padding(24.dp),
style = MaterialTheme.typography.h1
)
}
Button(onClick = { ThemeState.isLight = !ThemeState.isLight }) {
Text(text = "Change Theme IsLight:${ThemeState.isLight}")
}
}
}
At the moment I don't have any Idea why your code not works, I'll update this answer when I find out.
but instead of using if else for colors parameter use it for the whole MaterialTheme like this and it will work:
#Composable
fun MyAppTheme(
children: #Composable() () -> Unit
) {
if (ThemeState.isLight) {
MaterialTheme(colors = themeColorsLight) {
children()
}
} else {
MaterialTheme(colors = themColorDark) {
children()
}
}
}
Update:
seems that it's bug in Jetpack Compose dev11, I tried in dev12 and it works there.
NOTE 1:
#Model has been deprecated in dev 12
change your ThemeState to
object ThemeState {
var isLight by mutableStateOf(true)
}
more information: https://android-review.googlesource.com/c/platform/frameworks/support/+/1311293
NOTE 2
There are some problems with auto Import in recent versions of AndroidStudio
If the Idea throws error: Type 'MutableState<TypeVariable(T)>' has no method 'getValue(ThemeState, KProperty<*>)' and thus it cannot serve as a delegate
Import getValue and SetValue manually.
import androidx.compose.getValue
import androidx.compose.setValue
Since 0.1.0-dev16 use these imports:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
Use AppCompatDelegate class
Step 1: Define a state that will point to Light mode initially.
object ThemeState {
var darkModeState : MutableState<Boolean> = mutableStateOf(false)
}
Note : Whenever this state will be changed, all the methods reading this state value will also be called.
Step 2 : Define a variable for reading state
val isDark = ThemeState.darkModeState.value
Step 3 : Now change Theme mode from Dark to Light and vice versa as follows
Button(onClick = {
val theme = when(isDark){
true -> AppCompatDelegate.MODE_NIGHT_NO
false -> AppCompatDelegate.MODE_NIGHT_YES
}
AppCompatDelegate.setDefaultNightMode(theme)
ThemeState.darkModeState.value = !isDark
}) {
Text(text = "Theme Toggle Button")
}
As you can see here, I'm changing app theme every time Theme Toggle Button is clicked.

Why is the Kotlin Synthetic is Null unless I use an explicit scope

So I have a bit of code here that used to work 1 month ago.
profile_clickable.throttleClicks {
logger.logEvent(PageTags.MENU_PROFILE_NAV)
edit_picture_button.visibility = View.GONE
ProfileActivity.start(this#HomeMenuActivity, avatar.transition(), username.transition())
}
This code now fails with an NPE on edit_picture_button, avatar, and username which are all Kotlin synthetics.
When I add an explicit call to each of those items (see below) suddenly it works.
profile_clickable.throttleClicks {
logger.logEvent(PageTags.MENU_PROFILE_NAV)
this#HomeMenuActivity.edit_picture_button.visibility = View.GONE
ProfileActivity.start(this#HomeMenuActivity, this#HomeMenuActivity.avatar.transition(), this#HomeMenuActivity.username.transition())
}
throttleClicks is an extension method that does this:
fun View.throttleClicks(
windowDurationMs: Long = 800,
onClick: View.() -> Unit
) {
setOnClickListener(object : View.OnClickListener {
// Set lastClickTime to - windowDurationMs to ensure the first click won't be throttled.
var lastClickTime = -windowDurationMs
override fun onClick(v: View?) {
val time = SystemClock.elapsedRealtime()
if (time - lastClickTime >= windowDurationMs) {
lastClickTime = time
onClick()
}
}
})
}
Why do I suddenly have to use an explicit scope to avoid NPEs?
Because you use synthetics in functiun of type View.() -> Unit.
So this in function is view on whitch you apply this function (profile_clickable).
Kotlin synthetics works like
val View.profile_clickable: ImageView get() {
if (cache exists) {
return cache
}
return this.findViewById(R.id.profile_clickable)
}
profile_clickable hasn't any childs, so there will be exception.
You can use this code:
profile_clickable.throttleClicks {
logger.logEvent(PageTags.MENU_PROFILE_NAV)
this#HomeMenuActivity.run {
edit_picture_button.visibility = View.GONE
ProfileActivity.start(this, avatar.transition(), username.transition())
}
}

Android Data Binding Problem: Missing return statement in generated code while using with a custom view?

I am using data binding in my current application, and so far so good. However, when I tried to use it with a custom data binding adapter I wrote for my custom view, I got an error from auto generated file as the title says, missing return statement. This error does not occur when I used this data binding on only one view, but more than one gives the error. Below are my custom view and adapters, and usage in xml file. I already checked the answer of kinda duplicated question but it doesn't worked in my case, and does not have enough explanation.
class NeonTextView(context: Context, attrs: AttributeSet) : TextView(context, attrs) {
private val drawableClear: Drawable?
get() = ContextCompat.getDrawable(context, R.drawable.ic_clear)
lateinit var actionMethod: () -> Unit
lateinit var clearMethod: () -> Unit
var hasActionMethod = false
var hasClearMethod = false
init {
setupAttributes(attrs)
}
private fun setupAttributes(attrs: AttributeSet) {
val typedArray =
context.theme.obtainStyledAttributes(attrs, R.styleable.NeonTextView, 0, 0)
hasActionMethod = typedArray.getBoolean(
R.styleable.NeonTextView_hasActionMethod,
false
)
hasClearMethod = typedArray.getBoolean(
R.styleable.NeonTextView_hasClearMethod,
false
)
typedArray.recycle()
}
override fun onTextChanged(
text: CharSequence?,
start: Int,
lengthBefore: Int,
lengthAfter: Int
) {
text?.let { text ->
drawableClear?.let {
it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
}
setCompoundDrawablesWithIntrinsicBounds(
null,
null,
if (text.isNotEmpty()) drawableClear!! else null,
null
)
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
return when (it.action) {
ACTION_DOWN -> return true
ACTION_UP -> {
if (compoundDrawables[2] == null && hasActionMethod) {
actionMethod()
} else {
if (it.x > (width - paddingRight - compoundDrawables[2]!!.intrinsicWidth)) {
if (hasClearMethod) clearMethod()
text = ""
} else {
if (hasActionMethod) actionMethod()
}
}
performClick()
true
}
else -> false
}
}.run {
return false
}
}
override fun performClick(): Boolean {
super.performClick()
return true
}
}
And here is my binding adapters for binding methods that are used inside this custom text view:
#BindingAdapter("actionMethod")
fun NeonTextView.setActionMethod(actionMethod: () -> Unit) {
this.actionMethod = actionMethod
this.hasActionMethod = true
}
#BindingAdapter("clearMethod")
fun NeonTextView.setClearMethod(clearMethod: () -> Unit) {
this.clearMethod = clearMethod
this.hasClearMethod = true
}
And here is how I applied inside xml file:
<com.android.platform.NeonTextView
android:id="#+id/textViewSectionField"
style="#style/HeaderTextView.SubHeader"
app:hasActionMethod="true"
app:actionMethod="#{() -> viewModel.getDepartmentList()}"/>
Any ideas why I am getting error from generated file when I used this binding in more than one view inside xml?
Thanks in advance.
The problem is with Java<->Kotlin compatibility. In Kotlin if you declare function
interface Func {
fun test(): Unit {
}
}
And use it from java
class FuncImpl implements Func {
#Override
public Unit test() {
return Unit.INSTANCE;
}
}
Note, please, that in that case in java code you need return statement. The same is for lambdas.
So when you set up lambda from xml using databinding, it's treated as java's lambda, so generated code wasn't correctly processed in that case.

Remove data from list while iterating kotlin

I am new to kotlin programming. What I want is that I want to remove a particular data from a list while iterating through it, but when I am doing that my app is crashing.
for ((pos, i) in listTotal!!.withIndex()) {
if (pos != 0 && pos != listTotal!!.size - 1) {
if (paymentsAndTagsModel.tagName == i.header) {
//listTotal!!.removeAt(pos)
listTotal!!.remove(i)
}
}
}
OR
for ((pos,i) in listTotal!!.listIterator().withIndex()){
if (i.header == paymentsAndTagsModel.tagName){
listTotal!!.listIterator(pos).remove()
}
}
The exception which I am getting
java.lang.IllegalStateException
use removeAll
pushList?.removeAll { TimeUnit.MILLISECONDS.toMinutes(
System.currentTimeMillis() - it.date) > THRESHOLD }
val numbers = mutableListOf(1,2,3,4,5,6)
val numberIterator = numbers.iterator()
while (numberIterator.hasNext()) {
val integer = numberIterator.next()
if (integer < 3) {
numberIterator.remove()
}
}
It's forbidden to modify a collection through its interface while iterating over it. The only way to mutate the collection contents is to use Iterator.remove.
However using Iterators can be unwieldy and in vast majority of cases it's better to treat the collections as immutable which Kotlin encourages. You can use a filter to create a new collections like so:
listTotal = listTotal.filterIndexed { ix, element ->
ix != 0 && ix != listTotal.lastIndex && element.header == paymentsAndTagsModel.tagName
}
The answer by miensol seems perfect.
However, I don't understand the context for using the withIndex function or filteredIndex. You can use the filter function just by itself.
You don't need access to the index the list is at, if you're using
lists.
Also, I'd strongly recommend working with a data class if you already aren't. Your code would look something like this
Data Class
data class Event(
var eventCode : String,
var header : String
)
Filtering Logic
fun main(args:Array<String>){
val eventList : MutableList<Event> = mutableListOf(
Event(eventCode = "123",header = "One"),
Event(eventCode = "456",header = "Two"),
Event(eventCode = "789",header = "Three")
)
val filteredList = eventList.filter { !it.header.equals("Two") }
}
The following code works for me:
val iterator = listTotal.iterator()
for(i in iterator){
if(i.haer== paymentsAndTagsModel.tagName){
iterator.remove()
}
}
You can also read this article.
People didn't break iteration in previous posts dont know why. It can be simple but also with extensions and also for Map:
fun <T> MutableCollection<T>.removeFirst(filter: (T) -> Boolean) =
iterator().removeIf(filter)
fun <K, V> MutableMap<K, V>.removeFirst(filter: (K, V) -> Boolean) =
iterator().removeIf { filter(it.key, it.value) }
fun <T> MutableIterator<T>.removeFirst(filter: (T) -> Boolean): Boolean {
for (item in this) if (filter.invoke(item)) {
remove()
return true
}
return false
}
Use a while loop, here is the kotlin extension function:
fun <E> MutableList<E>.removeIfMatch(isMatchConsumer: (existingItem: E) -> Boolean) {
var index = 0
var lastIndex = this.size -1
while(index <= lastIndex && lastIndex >= 0){
when {
isMatchConsumer.invoke(this[index]) -> {
this.removeAt(index)
lastIndex-- // max is decreased by 1
}
else -> index++ // only increment if we do not remove
}
}
}
Typically you can use:
yourMutableCollection.removeIf { someLogic == true }
However, I'm working with an Android app that must support APIs older than 24.
In this case removeIf can't be used.
Here's a solution that is nearly identical to that implemented in Kotlin Collections that doesn't rely on Predicate.test - which is why API 24+ is required in the first place
//This function is in Kotlin Collections but only for Android API 24+
fun <E> MutableCollection<E>.removeIff(filter: (E) -> Boolean): Boolean {
var removed = false
val iterator: MutableIterator<E> = this.iterator()
while (iterator.hasNext()) {
val value = iterator.next()
if (filter.invoke(value)) {
iterator.remove()
removed = true
}
}
return removed
}
Another solution that will suit small collections. For example set of listeners in some controller.
inline fun <T> MutableCollection<T>.forEachSafe(action: (T) -> Unit) {
val listCopy = ArrayList<T>(this)
for (element: T in listCopy) {
if (this.contains(element)) {
action(element)
}
}
}
It makes sure that elements of collection can be removed safely even from outside code.

Categories

Resources