I have a Custom component in android with this layout.
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatEditText
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
when using in another layout I find editText by this code.(Espresso)
val editText = onView(
allOf(withId(R.id.editText)
, isDescendantOfA(withId(R.id.mainLayout))
, isDescendantOfA(withId(R.id.mobileEdt))
)
)
I use this custom Component in all app and many layouts.
can I minify or convert to function in my app for doesn't write again and again?
Maybe I change the component layout, so I have to edit all withId in all test.
Your component has probably a class name. Let's say CustomEditText.
In that case you can implement a BoundedMatcher based custom matcher, which makes sure that it will only match view instances of your CustomEditText.
Simple implementation could look like this:
fun customEditWithId(idMatcher: Matcher<Int>): Matcher<View> {
return object : BoundedMatcher<View, CustomEditText>(CustomEditText::class.java!!) {
override fun describeTo(description: Description) {
description.appendText("with id: ")
idMatcher.describeTo(description)
}
override fun matchesSafely(textView: CustomEditText): Boolean {
return idMatcher.matches(textView.id)
}
}
}
then your assertion looks like this:
onView(customEditWithId(0)).perform(click());
it's obviously not a descendant of R.id.mobileEdt ...
val editText = onView(allOf(
withId(R.id.editText),
isDescendantOfA(withId(R.id.mainLayout))
))
Related
in my current project there's an EditText with fixed layout_width and layout_height, called exercise that is expanded downwards programmatically: One line of text (String) + "\n" is added to the EditText.
Sometimes the line added to the EditText, let's call it element, is too long to fit inside the full width of the object so it's splitted into a new line.
The thing is I would either like the lines' text size in exercise to be resized to fit the EditText's width or have a clear visible distance between each line (element), but just not inside the newline due to not fitting inside the exercise's width.
Therefore I googled as much as I could and tried out every possible solution I could find today.
What I tried:
Using either EditText as the object and android:autoSizeTextType="uniform" & android:inputType="textMultiLine|textCapSentences"as attributes or androidx.appcompat.widget.AppCompatEditText, accompanied by the attributes app:autoSizeMaxTextSize="28sp", app:autoSizeMinTextSize="8sp"and app:autoSizeStepGranularity="1sp"
(with a device that supports just exactly API 26)
using other types of text objects
using lineSpacingExtra to insert some spacing. This unfortunately also inserted the spacing between the wrapped/ splitted line so the original element's line that got splitted by wrapping inside the EditText had the spacing aswell.
That's where I am now. I can't get the text size be reduced automatically when the line would be too wide for the EditText's width.
I could supply the full XML, if needed.
I'm grateful for any hint that could help here. Thanks in advance!
Here's a really basic RecyclerView implementation (using view binding, let me know if you're not familiar with that - you can just findViewById all the things instead):
class MainFragment : Fragment(R.layout.fragment_main) {
lateinit var binding: FragmentMainBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentMainBinding.bind(view)
with(binding) {
val adapter = MyAdapter()
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
addButton.setOnClickListener {
val item = textEntry.text.toString()
if (item.isNotBlank()) {
adapter.addItem(item)
textEntry.text.clear()
}
}
}
}
}
class MyAdapter : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
private var data: List<String> = emptyList()
fun addItem(item: String) {
data = data + item
notifyItemInserted(data.lastIndex)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = ItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.binding.textView.text = data[position]
}
override fun getItemCount(): Int = data.size
class ViewHolder(val binding: ItemViewBinding) : RecyclerView.ViewHolder(binding.root)
}
fragment_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:gravity="center"
android:padding="10dp">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/textEntry"
/>
<EditText
android:id="#+id/textEntry"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:singleLine="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="#id/addButton"
/>
<Button
android:id="#+id/addButton"
android:text="ADD"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
item_view.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
>
<TextView
android:id="#+id/textView"
app:autoSizeTextType="uniform"
android:layout_width="match_parent"
android:layout_height="48sp"
android:maxLines="1"
android:gravity="center_vertical"
/>
</FrameLayout>
It's pretty simple - you have a text entry field and a button to add the contents as a new line. The button passes the contents to addItem on the adapter, which appends it to the list of lines in data. The RecyclerView just displays all the items in data, using a ViewHolder layout that has an auto-sizing TextView to scale each item.
Ideally you'd want to persist data somehow (e.g. the Add button passes the new data to a ViewModel, stores it somehow, updates the current list which the adapter has observed so it updates whenever there's a change) - I just left it as a basic proof of concept. Also, it's easier to store separate items if they're kept separate - you can always serialise it by joining them into a single string if you really want! But generally you wouldn't want to do that
edit - since you're having trouble with the setTypeface thing, this is all it is:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
with(holder.binding.textView) {
val styled = position % 2 == 0
text = data[position]
setTypeface(typeface, if (styled) Typeface.BOLD else Typeface.NORMAL)
setTextColor(if (styled) Color.RED else Color.BLACK)
}
}
The logic is just styling alternate items differently, but hopefully you get the idea. You decide how to style a given item depending on what it is, and then you apply that style by setting attributes as appropriate. It's always an "if this is true do A, otherwise do B" situation, so you're always setting the attribute one way or the other. You never only set it for one case, because then you're leaving old state displayed if it's not that case.
It's more complicated, but you also have the option of creating different ViewHolders (with their own XML layouts) for different kinds of item. So instead of having a single ViewHolder that has to work with everything, where you have to reconfigure things like all the styling in onBindViewHolder depending on which type of item is displayed, you can just have different ViewHolders with different styling, different layouts etc:
// creating a sealed class so we can say our adapter handles a MyViewHolder type,
// and we can have a specific set of possible subclasses of that
sealed class MyViewHolder(view: View) : RecyclerView.ViewHolder(view)
class HeaderViewHolder(val binding: HeaderItemBinding) : MyViewHolder(binding.root)
class ItemViewHolder(val binding: ItemViewBinding) : MyViewHolder(binding.root)
// the Adapter now uses the MyViewHolder type (which as above, covers a couple of different
// ViewHolder classes we're using)
class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
private var data: List<String> = emptyList()
fun addItem(item: String) {
data = data + item
notifyItemInserted(data.lastIndex)
}
// some identifiers for the different ViewHolder types we're using
private val HEADER_TYPE = 0
private val ITEM_TYPE = 1
override fun getItemViewType(position: Int): Int {
// Work out what kind of ViewHolder the item in this position should display in.
// This gets passed to onCreateViewHolder, where you create the appropriate type,
// and that type of ViewHolder is what gets passed into onBindViewHolder for this position
return if (data[position].startsWith("Section")) HEADER_TYPE else ITEM_TYPE
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val inflater = LayoutInflater.from(parent.context)
// creating the appropriate ViewHolder instance depending on the type requested
return when(viewType) {
HEADER_TYPE -> HeaderViewHolder(HeaderItemBinding.inflate(inflater, parent, false))
ITEM_TYPE -> ItemViewHolder(ItemViewBinding.inflate(inflater, parent, false))
else -> throw RuntimeException("Unhanded view type!")
}
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
// The type of MyViewHolder passed in here depends on what getItemViewType returns
// for this position - binding is a different type in each case,
// because it's been generated from different layouts
when(holder) {
is HeaderViewHolder -> holder.binding.textView.text = data[position]
is ItemViewHolder -> holder.binding.textView.text = data[position]
}
}
override fun getItemCount(): Int = data.size
}
(You could be a bit more clever than this, but just to illustrate the general idea!)
That's using the same item_view.xml as before, and a header_item.xml variation on that (but you could have literally anything, they're completely separate layouts, completely separate ViewHolders):
header_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
>
<TextView
android:id="#+id/textView"
app:autoSizeTextType="uniform"
android:layout_width="match_parent"
android:layout_height="48sp"
android:layout_weight="1"
android:textStyle="bold"
android:textColor="#DD1100"
android:maxLines="1"
android:gravity="center_vertical"
/>
</LinearLayout>
So instead of having to "redesign" one ViewHolder in code to go back and forth between different item types and their styling, You can just use two differently-styled layouts. It's a bit more work to set up, but it can be neater and much more flexible when you have completely independent things - especially if you want to give them different functionality. It depends whether it's worth it to you, or if you're happy to just have a single ViewHolder and restyle things in code, hide or show elements etc.
you can try something like this
if(et.getText().length()>10) {
et.setTextSize(newValue)
The application has a custom email input field, how can I check that it displays the drawable icons in android test (espresso)
xml:
<com.my.mobile.kit.input.InputGeneral
android:id="#+id/login_email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
app:kitEndIconDrawable="#drawable/kit_ic_cleanitem_24"
app:kitEndIconMode="clear_text"
app:kitEndIconTint="#color/baseDeepGreyOne"
app:kitLabelText="#string/m_auth_login_email_hint"
app:kitLabelVisibilityMode="manual"
app:kitPlaceholderText="#string/m_auth_login_email_hint"
app:layout_constraintTop_toTopOf="parent" />
You can try using isDescendantOfA
onView(
allOf(
withId(R.id.kitEndIconDrawable),
isDescendantOfA(withId(R.id.login_email))
)
).check(ViewAssertions.matches(/* check matches R.drawable.kit_ic_cleanitem_24 */))
Where kitEndIconDrawable is the id of the ImageView inside your custom view
Matching a drawable is a different topic, there are several approaches, you can use tags or some check the Bitmap, see these questions:
Using Espresso to test drawable changes
How to use espresso to test vector drawables
This is a custom view then you should define custom ViewAction for perform click on that icon.
Something like this :
object OnCloseItemClick : ViewAction {
override fun getConstraints(): org.hamcrest.Matcher<View>? {
return ViewMatchers.isAssignableFrom(InputGeneral::class.java)
}
override fun getDescription(): String {
return "whatever"
}
override fun perform(uiController: UiController?, view: View) {
val customView: InputGeneral = view as InputGeneral
customView.close_item_id.performClick()
}
}
Now you can test this :
onView(withId(R.id.login_email)).perform(OnCloseItemClick)
I am trying to use last features from android - Kotlin, mvvm, architecture components, jetpack, databinding, one activity - many fragments approach with new navigation graph, but I am struggling with handling UI events in Fragments
In activity it is simple with kotlin-android-extensions
In XML I create a Button like this:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="clicked"/>
and in Activity I just write
fun clicked(view : View){
}
That's perfect, but unfortunately does not work in Fragment. Yes it is possible to still handle event in Activity and send it to fragment but that's ugly.
Next option is to use an interface,
public interface MyClickCallback{
void onLoginButtonClick();
}
implement it in fragment.
In xml it looks like this:
<variable
name="clickCallback"
type="com.test.MyClickCallback" />
then in fragment's onCreateView I have to set clickCallback to the fragment and finally I can use it
#Override fun onLoginButtonClick() {
}
Problem I have with this is to declare interface and on each new UI event enhance this interface and update fragment which implements it
Next option is RxView.clicks what looks really great with all its features. For example:
RxView.clicks(mSearchBtn)
.throttleFirst(2, TimeUnit.SECONDS)
.map(aVoid -> mSearchEdit.getText().toString().trim())
.filter(s -> !TextUtils.isEmpty(s))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
KeyBoardUtil.closeKeybord(mSearchEdit,
SearchActivity.this);
showSearchAnim();
clearData();
content = s;
getSearchData();
});
Problem here is that I have to bind it to the UI component - mSearchBtn. I do not want this :-). I do not want to have any UI component in fragment unless I really have to. I am always communicating with layout file via variables declared in layout like this
<data>
<variable
name="items"
type="java.util.List" />
</data>
I would love to bind it to variable declared in the XML which is set in Button
android:onClick="myclick"
But I did not find the way how to do it.
Anybody can help me maybe with other simple and nice options ?
In your databinding layout create a variable that is of type View.OnClickListener:
<variable
name="onClickListener"
type="android.view.View.OnClickListener" />
Set it to your View like this:
<View
...
android:onClickListener="#{onClickListener}"
... />
In your Fragment create the onClickListener and set it to the variable:
binding.onClickListener = View.OnClickListener {
/* do things */
/* like getting the id of the clicked view: */
val idOfTheClickedView = it.id
/* or get variables from your databinding layout: */
val bankAccount = binding.bankAccount
}
Or in Java:
binding.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
/* do things */
/* like getting the id of the clicked view: */
Int idOfTheClickedView = view.getId();
/* or get variables from your databinding layout: */
Object bankAccount = binding.getBankAccount()
}
});
it is simple with kotlin-android-extensions
It is indeed simple, but you are currently not using it to its fullest potential.
Setting click listeners in Kotlin is very easy, look:
fun View.onClick(clickListener: (View) -> Unit) {
setOnClickListener(clickListener)
}
And now thanks to synthetic imports in Kotlin-Android-Extensions:
<Button
android:id="#+id/myButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="#string/click_me"/>
and
import kotlinx.synthetic.blah.* // something like that
// Activity:
override fun onCreate(bundle: Bundle?) {
super.onCreate(bundle)
setContentView(R.layout.blah)
myButton.onClick {
// handle click event
}
}
// Fragment:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, bundle: Bundle?) = inflater.inflate(R.layout.blah, container, false)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
myButton.onClick {
// handle click event
}
}
But if you really want to use databinding and layouts for this, then set the callback lambda and inside the databinding layout file.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="activity" type="com.acme.MainActivity"/>
</data>
<RelativeLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="#+id/btnOpenSecondView"
android:text="Click me for second view!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:onClick="#{(v) -> activity.startNextActivity(v)}" />
</RelativeLayout>
</layout>
Assume an Android project in which I have this XML for two buttons:
<layout>
<data>
<variable name="viewModel" type="com.package.package.UploadPhotoViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:orientation="vertical"
android:padding="10dp">
<com.zoosk.zoosk.ui.widgets.ProgressButton
android:id="#+id/progressButtonChooseFromLibrary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{viewModel.choosePhotoText}"
android:onClick="#{viewModel::choosePhotoButtonClick}"
android:visibility="#{viewModel.choosePhotoVisibility}" />
<com.zoosk.zoosk.ui.widgets.ProgressButton
android:id="#+id/progressButtonTakePhoto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{viewModel.takePhotoText}"
android:onClick="#{viewModel::takePhotoButtonClick}"
android:visibility="#{viewModel.takePhotoVisibility}" />
</LinearLayout>
</layout>
And an accompanying ViewModel:
class UploadPhotoViewModel(resources: Resources) {
var onChoosePhotoButtonClicked: ((View) -> Unit)? = null
var onTakePhotoButtonClicked: ((View) -> Unit)? = null
fun choosePhotoButtonClick(v: View) {
onChoosePhotoButtonClicked?.invoke(v)
}
fun takePhotoButtonClick(v: View) = onTakePhotoButtonClicked?.invoke(v)
}
Noting particularly the difference between how choosePhotoButtonClick and takePhotoButtonClick are declared.
When building this project, Android's databinding will work properly for choosePhotoButtonClick, but throws an error with takePhotoButtonClick, saying that there's no method that matches the expected reference signature. Assuming these methods are created the same way under the hood, this should not happen, alas there must be some difference.
What exactly is the difference to these two declaration syntaxes? The official Kotlin documentation doesn't mention anything functional, only that it can serve as an additional way to declare that method.
If you use curly braces without specifying a return type, Your function is implicitly retuning Unit (this is Kotlin for void), so your method:
fun choosePhotoButtonClick(v: View) {
onChoosePhotoButtonClicked?.invoke(v)
}
Has signature of:
fun choosePhotoButtonClick(v: View) : Unit
(IDE will hint that return type "Unit" is redundant and can be ommited).
However using equals sign to shorthand a function infers the return type of expression on right hand side, for example:
fun itemCount() = items.size
Will infer return type of Int, so its signature is:
fun itemCount() : Int
In your case of :
fun takePhotoButtonClick(v: View) = onTakePhotoButtonClicked?.invoke(v)
Function can either return a null when onTakePhotoButtonClicked is null or return value of that method (Unit), which means your takePhotoButtonClick signature is:
fun takePhotoButtonClick(v: View) : Unit?
(OP) direct solution to question:
fun takePhotoButtonClick(v: View) = onTakePhotoButtonClicked?.invoke(v) ?: Unit
Can be considered less readable than the curly brace version, but this implies a return type of Unit instead of Unit? and will be picked up correctly by databinding.
I have a text view to which I need to create a listener for onLongClick.
Right now for the respective viewmodel it has a function sendLogs() which deals with the logic for onClick. If I change onClick to onLongClick function never get call. Is there any way to make it work for onLongClick?
onClick is directly linked to my model class function but not the onLongClick. So I think model class binding is correct but I may need some extra work here.
<data>
<import type="android.view.View" />
<variable
type="com.aaa.bbb.viewmodel.SystemSettingsViewModel"
name="systemSettings"
</variable>
</data>
<TextView
android:gravity="end"
android:id="#+id/tv_logging"
android:layout_centerVertical="true"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_width="wrap_content"
android:onClick="#{() -> systemSettings.sendLogs()}"
android:text="#string/enable_logs"
android:textAlignment="viewEnd" />
I managed to work it correctly. I doubt this is properly documented.
In xml
android:onLongClick="#{(view) -> presenter.onLongClickOnHeading(view)}"
In presenter viewmodel class
public boolean onLongClickOnHeading(View v) {
//logic goes here
return false;
}
Note: this method signature should be exactly in this format. Otherwise biding errors will be thrown at runtime.
Here is the complete the code.
There is no such attribute for long click. So we have to create a binding adapter.
BindingUtils.kt
object BindingUtils {
private const val ON_LONG_CLICK = "android:onLongClick"
#JvmStatic
#BindingAdapter(ON_LONG_CLICK)
fun setOnLongClickListener(
view: View,
func : () -> Unit
) {
view.setOnLongClickListener {
func()
return#setOnLongClickListener true
}
}
}
Layout
<androidx.constraintlayout.widget.ConstraintLayout
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:onLongClick="#{() -> vm.onLongClick()}"/>
For it to work, the part in parenthesis has to match the method signature from the interface View.OnLongClickListener which looks like this :
boolean onLongClick(View view);
So this is how I got it to work :
<View
...
android:onLongClick="#{(view) -> listener.onLongClick(view, viewmodel)}"/>
...
In the xml section, you must refer to the Boolean return function, such as the following code, so as not to get into trouble.in build project android studio
in xml
android:onLongClick="#{(view) -> presenter.onLongClick(view)}"
in java
public boolean onLongClick(View v) {
return false;
}
You should look into this document
OnLongClick is as easy as onClick
Within your SystemSettingsViewModel you can have
public boolean onLongClick(){}
and in xml
android:onLongClick="#{() -> presenter.onLongClick()}"
As mentioned in the Google documentation Link there is no problem with what you wrote.
This is a sample of OnLongClick in XML:
android:onLongClick="#{(theView) -> presenter.onLongClick(theView, task)}"
class Presenter {
fun onLongClick(view: View, task: Task): Boolean { }
}