Say for instance I have the following variable in my data bound XML.
<layout ...>
<data>
<variable name="listener" type="com.xyz.Listener" />
<!-- and other variables -->
</data>
...
</layout>
I use this variable in every single one of my data-bound layouts, and I need to access it in almost every single one of my #BindingAdapter. For instance, my binding adapters mostly look like this.
#BindingAdapter("board")
fun setBoard(view: TextView, board: Board) {
view.setText(board.name)
view.setOnClickListener {
listener.onBoardClicked(board)
}
}
#BindingAdapter("topic")
fun setTopic(view: TextView, topic: Topic) {
view.setText(topic.name)
view.setOnClickListener {
listener.onTopicClicked(topic)
}
}
// and a few others like that
and I use them like this
<TextView ... app:board="#{board}" ... />
<TextView ... app:topic="#{topic}" ... />
What I need here is a way to access that listener variable declared in the data block to all of my binding adapters. Is there a way to do that without manually passing it as a second variable every single time?
// I know I can do this - looking for an alternative
#BindingAdapter({"board", "listener"})
fun setBoard(view: TextView, board: Board, listener: Listener) {
view.setText(board.name)
view.setOnClickListener {
listener.onBoardClicked(board)
}
}
I am using Kotlin here, but a solution in Java works just fine for me as well.
After doing some more research, I've just discovered the DataBindingComponent interface and it solves precisely the problem I was having. Apparently, if you make your binding adapters instance methods rather than static, the compiler will take the class you declared it in, and add it as a property of DataBindingComponent. So I made my binding adapters instance methods, and injected the variable I wanted via the constructor.
class Binding(val listener: Listener) {
#BindingAdapter("board")
fun setBoard(view: TextView, board: Board) {
view.setText(board.name)
view.setOnClickListener {
listener.onBoardClicked(board)
}
}
#BindingAdapter("topic")
fun setTopic(view: TextView, topic: Topic) {
view.setText(topic.name)
view.setOnClickListener {
listener.onTopicClicked(topic)
}
}
}
After building, the compiler generated the following interface
package android.databinding;
public interface DataBindingComponent {
com.my.package.Binding getBinding();
}
I then completed the cycle by making the binding class extend this interface and return itself
class Binding(val listener: Listener) : DataBindingComponent {
override fun getBinding(): Binding {
return this
}
// all the other stuff
}
This allows me to pass it as an argument when inflating views, and as such I no longer have to even declare listener as an XML variable. I can just declare the Binding instance
val bindingComponent = Binding(object : Listener {
// implement listener methods here
})
and pass it when inflating the layout
// now I can use it in activities
DataBindingUtil.setContentView<MyActivityBinding>(
this, R.layout.my_activity, bindingComponent)
// ...or in fragments
DataBindingUtil.inflate<MyFragmentBinding>(
inflater, R.layout.my_fragment, parent, false)
// ...or any other place DataBindingUtil allows
Related
I have recyclerView with 2 view types (item and FOOTER). I need to show my footer on the bottom of screen even if no items or items size is 1. Is it possible to implement it? Now my footer is showing after last item, but I need show footer always on the bottom.
You need a data structure that allows you to do so, and then you need the view holders for supporting it, once that is done handling the conditional flows on the adapter and should be good to go.
Usually, in Kotlin we use a sealed class which allows very good type control
sealed class AdapterRow {
data class RegularItem(/*your values*/) : AdapterRow()
data class FooterItem(/*your values*/) : AdapterRow()
//if the footer is always the same maybe object FooterItem : AdapterRow() is more suitable
}
It's a nice "trick" to have the sealed descendants inside so that way the sealed parent makes a domain space name, then you call them like this AdapterRow.RegularItem(...). If you don't like that, the sealed class has one constraint, descendants must be on the same file.
Then you need a view holder for supporting each type (view holder and the view in the layout if needed). In this case we are gonna use an abstract class to take advantage of the polymorphism and abstract methods
//you could use binding here and then the implementation define the binding type
abstract class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) {
abstract fun bind(row: AdapterRow)
}
And then the children:
class RegularItemViewHolder(view: View) : BaseViewHolder(view) {
override fun bind(row: AdapterRow) {
//validate the data you are receiving is correct
if (row !is AdapterRow.RegularItem) return
//do your data bindings, view findings or whatever
}
}
With the above you can deduce the other view holder, now the adapter methods
getItemViewType(position: Int) {
when(getItem(position)) {
is AdapterRow.RegularItem -> R.layout.REGULAR_ITEM_LAYOUT
is AdapterRow. FooterItem -> R.layout.FOOTER_ITEM_LAYOUT
}
}
onCreateViewHolder(...) {
return when (viewType) {
R.layout.REGULAR_ITEM_LAYOUT -> {
RegularItemViewHolder(...)
}
R.layout.FOOTER_ITEM_LAYOUT {
//same but for footer
}
else -> throw RuntimeException("Unsupported view Holder)
}
}
onBindViewHolder(...) {
holder.bind(getItem(position))
}
The data construction part is the last thing, in somewhere else, your fragment, the view model, etc, you have to create the data structure as you need. By example:
fun makeRows(texts: List<String>): List<AdapterRow> {
val rows = mutableListOf<AdapterRow>()
texts.forEach { text ->
//do some mapping from what ever is your source in this case strings
AdapterRow.RegularItem(...text)
}
//so it doesn't matter if the source of data is empty at the end you always add the footer
rows.add(AdapterRow.FooterItem)
}
And then is just passing the data to the adapter, if you are using ListAdapter
val rows = someClass.makeRows(....)
yourAdapter.submitList(rows)
With Android data-binding framework, I understand that you can pass an object that extends baseObservable to the layout xml, use #Bindable on getters and do notifyPropertyChanged(BR.xxx) to have the related part re-evaluated.
What I don't understand is this: if you don't use most the stuff above and just call the getter directly in xml, when would it be evaluated?
Here's the code:
my_widget.xml
<layout 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">
<data>
<variable
name="someViewModel"
type="com.example.SomeViewModel" />
</data>
<androidx.cardview.widget.CardView>
<View
android:id="#+id/testView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="#{someViewModel.getName() ? View.VISIBLE : View.GONE}" />
</androidx.cardview.widget.CardView>
</layout>
MyView.java
MyWidgetBinding binding = MyWidgetBinding.inflate(LayoutInflater.from(mContext), parent, false);
binding.setSomeViewModel(someViewModel);
Questions:
If someViewModel.name ever changes, does the testView's visibility refreshes?
When does someViewModel.getName() get evaluated or how often?
If the expression is more complicated, something like:
android:visibility="#{func(otherVariable, someViewModel.getName()) ? View.VISIBLE : View.GONE}", say otherVariable is another variable defined in data section above, if somehow otherVariable gets re-set, then someViewModel.getName() will get evaluated and testView will reflect the latest visibility value, correct?
Following up on question 3, if otherVariable is changed to otherVariable.a where a is a 'bindable' field and notifyPropertyChanged(BR.a) is called in otherVariable then someViewModel.getName() will also get re-evaluated and testView will reflect the latest visibility value, correct?
Additionally, if I re-set someViewModel by calling binding.setSomeViewModel() but pass in the SAME someViewModel instance, does it do anything? Will the expression get re-evaluated?
I suggest you to create custom binding adapter for mutable visibility and use LiveData to update the visibility.
Code :
#BindingAdapter("mutableVisibility")
fun setMutableVisibility(view: View, visibility: MutableLiveData<Boolean>) {
val parentActivity: AppCompatActivity? = view.getParentActivity()
if (parentActivity != null) {
visibility.observe(
parentActivity,
Observer { value -> if (value) view.visibility = View.VISIBLE
else view.visibility = View.GONE})
}
}
To get the parent activity create ActivityExtensions.kt file and add the following function in it:
fun View.getParentActivity(): AppCompatActivity?{
var context = this.context
while (context is ContextWrapper) {
if (context is AppCompatActivity) {
return context
}
context = context.baseContext
}
return null
}
And in the ViewModel :
//Other code here...
val itemVisibility = MutableLiveData<Boolean>()
//Other logic here to init itemVisible
if(itemVisibile) itemVisibility.value = true else itemVisibility.value = false
And finaly the layoutItem :
<View
android:id="#+id/testView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:mutableVisibility ="#{viewModel.itemVisibility}" />
If someViewModel.name ever changes, does the testView's visibility
refreshes?
it depends on the underlying technology you are using. LiveData? Yes BaseObservable you have to manually notify that the observed property changed.
When does someViewModel.getName() get evaluated or how often?
LiveData ? when you set/post a value. BaseObservable when you notify it
Same as point 2
If you change the underlying value and notify this change properly, it will get propagated accordingly. If you change the instance of the observed object it will not.
I know probably there is no clear answer for this question.
But I would like to know Your opinions and maybe new ideas.
I'm wondering which of the following options is the best/right/correct way to build the app-level Dagger Component in Application class.
Example 1:
public class MyApp extends Application {
private NetComponent mNetComponent;
#Override
public void onCreate() {
super.onCreate();
mNetComponent = DaggerNetComponent.builder()
.appModule(new AppModule(this))
.netModule(new NetModule("https://api.github.com"))
.build();
}
public NetComponent getNetComponent() {
return mNetComponent;
}
}
Usage:
((MyApp) getApplication()).getNetComponent().inject(this);
Example 2:
class MyApplication extends Application {
private static MyComponent component;
#Override
void onCreate() {
component = DaggerMyComponent.builder()
.contextModule(new ContextModule(getApplicationContext()))
.build();
}
public static MyComponent getMyComponent() {
return component;
}
}
Usage:
MyApplication.getMyComponent().inject(this)
Example 3:
class CustomApplication: Application() {
lateinit var component: SingletonComponent
private set
override fun onCreate() {
super.onCreate()
INSTANCE = this
component = DaggerSingletonComponent.builder()
.contextModule(ContextModule(this))
.build()
}
companion object {
private var INSTANCE: CustomApplication? = null
#JvmStatic
fun get(): CustomApplication = INSTANCE!!
}
}
Then:
class Injector private constructor() {
companion object {
#JvmStatic
fun get() : SingletonComponent = CustomApplication.get().component
}
}
Usage:
Injector.get().catRepository()
Example 4:
class App : Application() {
var repositoryComponent: RepositoryComponent? = null
var appComponent: AppComponent? = null
override fun onCreate() {
super.onCreate()
instance = this
appComponent = DaggerAppComponent.builder().application(this).build()
repositoryComponent = DaggerRepositoryComponent.builder().build()
}
companion object {
private var instance: App? = null
fun get(): App {
return instance!!
}
}
}
Usage:
App.get().repositoryComponent!!.inject(this)
What do you think about this? Is there any better / cleaner way to do this? Maybe provided examples are fine? Or maybe just one of them?
I will be grateful for any good examples / tips / advices.
Thanks!
Okay, no one answered in 5 days so it's my turn, despite my bias :p
Option #1
((MyApp) getApplication()).getNetComponent().inject(this);
It's an "ok" version of doing things, except for two things.
First, the name. NetComponent isn't really for networking, it's the app-global singleton component, so it should be either called SingletonComponent or AppComponent. But naming it NetComponent is disingenuous, it's typically responsible for everything else too.
Second problem is that you need a reference to Context to access your dependency graph, making Context actually be a dependency rather than it being provided to you.
Option #2
MyApplication.getMyComponent().inject(this)
This is a perfectly fine way of doing things, but you need to know that to reach your object graph, you need to access the static method of MyApplication.
Option #3
Injector.get().inject(this)
Internally, this solution actually just calls over to get the app component, public static AppComponent get() { return MyApplication.getInstance().getComponent(); }
The benefit is that getComponent() is exposed via an instance method of Application, so it could be theoretically swapped out.
Also, invoking a method on something called Injector.get() is more obviously an "injector" than, well, an application class.
As for whether you use .catRepository() or .inject(this), it's up to you; but I personally prefer calling the provision methods to get the deps in Activity/Fragment, because listing the member-injection targets adds a lot of clutter to the component over time.
4.)
App.get().repositoryComponent!!.inject(this)
You can ditch the !! if repositoryComponent is a lateinit var.
Having two components for the same scope (and therefore two different object graphs) will only cause trouble, out of all of the options, this is the worst.
In my opinion, the 3rd option is the best. Technically it's the same as option #2 with an additional "indirection" through the instance method of Application that actually returns the component.
I used the anko library to create a login view.
class SingInView : AnkoComponent<SingleInActivity> {
override fun createView(ui: AnkoContext<SingleInActivity>) = with(ui) {
verticalLayout {
lparams(width = matchParent, height = matchParent)
textView("Member Login")
editText {
hint = "E-mail"
}
editText {
hint = "PassWord"
}
button("Login")
}
}
}
and SingleInActivity.kt
class SingleInActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState)
SingInView().setContentView(this)
and MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startActivity(new Intent(this, SingInView.class));
finish();
}
}
current My app MainActivity -> SingleInActivity -> SingInView .
of course it can be made simply.
but there is a condition
1. MainActivity is java (kotlin prohibition)
2. use only MainActivity, SingInView.
How to solve this problem?
How to call the Anko class directly from a Java class
If you dig through the Anko source code you'll quickly find this:
interface AnkoComponent<in T> {
fun createView(ui: AnkoContext<T>): View
}
And from the wiki (where MyActivityUI is the component): MyActivityUI().setContentView(this). Now, the AnkoComponent is just an interface and the setContentView method is an extension function that returns createView.
Anyways, the setContentView extension function passes the last variable of the AnkoContextImpl as true. The last variable is whether or not to actually set the content view, which is the reason the activity is passed in the first place.
TL;DR (and possibly more sensible summary of my point):
The component is not an Activity
The setContentView method is not a replacement for setContentView in an Activity; just a wrapper for it.
And since it isn't an activity, you can't use an intent into it. And, as a result of that, you cannot use it standalone. You need an activity. Now, you can of course use the regular approach, but there's also another way. Since the AnkoComponent itself doesn't have any fields, it can be serialized without much trouble. Just to clarify: some fields can be serialized even if it isn't serializable (all though some classes like Context cannot be serialized). Anyways, you create an activity:
class AnkoComponentActivity : AppCompatActivity(){//Can be a regular Activity too
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
val component = intent.getSerializableExtra("uiComponent") as AnkoComponent<AnkoComponentActivity>//The type has to match this activity, or setContentView won't allow passing `this`
component.setContentView(this)//The context of the activity doesn't get passed until this point, which enables the use of this system.
}
}
Or it's equivalent in Java:
public class AnkoComponentActivity extends AppCompatActivity {
public void onCreate(Bundle sis){
super.onCreate(sis);
AnkoComponent<AnkoComponentActivity> component = (AnkoComponent<AnkoComponentActivity>) getIntent().getSerializableExtra("uiComponent");
org.jetbrains.anko.AnkoContextKt.setContentView(component, this);//For reference, this is how you call Kotlin extension functions from Java
}
}
Note that any UI component sent to this class has to be declared with <AnkoComponentActivity>. In addition, the components have to implement Serializable. Otherwise they can't be passed through the Bundle. Alternatively, you can use ints or Strings as identifiers and use the value to pick which AnkoComponent to show.
All though, the absolutely easiest way is just creating one activity per component.
TL;DR: AnkoComponent is not an Activity, meaning you can't use intents into it. You have to use an Activity, but using Serializable enables you to pass the component through a bundle to an Activity made for manual creation of multiple AnkoComponents without specifying specific types.
This question is not manly for Android, but I will use an android example to explain.
I want to create a class that should override the method onTouchListener, to be used on any object that uses those touch methods.
Since after a touch input, the method starts a function, and since it's a class, it can be instantiate several times (and I want to be instantiate several times), I want to prevent that two instances are called at the same time.
I guess I could create a variable inside the class, that assured that if it's true, it can run the method, but I need to check it for all of the class instances.
And since I want to make this a library, I need to do this inside the class itself.
So my question is,
(Java Question) How can I know that a variable from any instance of a class is true?
(Android Question) Or if this is not possible, How can I prevent multiple touch events at the same time?
You could achieve that with static data and/or method for this class. A static element can be accessible from anywhere using the class name:
ex: MyClass.isAlreadyRunning();
you could for example store in an array all the running status of the instances, and create a static method that checks the array if one is already running.
You can think about making a Singleton Class. Wiki Link
Singleton classes allows to create ONLY one instance/object of that class and then reuse it.
You can use a static field in this context. A static field is identicaly for all instances of an class.
You can do it maybe like this:
public Example implements TouchInterface{
private static boolean actuallyRunning = false;
public void touchExecute(){
if(!actuallyRunning){
actuallyRunning = true;
callYourFunction();
actuallyRunning = false;
}
}
}
You can use singleton class if you want one one instance and if you want to handle the multiple touch events
you can disable as soon as your handler start and enable after that.
you can add a time span in normal practice it is one sec but it depends on you handler's complexity
Like this example:
// Make ur activity class to implement View.OnClickListener
public class MenuPricipalScreen extends Activity implements View.OnClickListener{
#Override
protected void onCreate(Bundle savedInstanceState) {
// setup listeners.
findViewById(R.id.imageView2).setOnClickListener(MenuPricipalScreen.this);
findViewById(R.id.imageView3).setOnClickListener(MenuPricipalScreen.this);
....
}
.
.
.
// variable to track event time
private long mLastClickTime = 0;
//View.OnClickListener.onClick method defination
#Override
public void onClick(View v) {
// Preventing multiple clicks, using threshold of 1 second
if (SystemClock.elapsedRealtime() - mLastClickTime < 1000) {
return;
}
mLastClickTime = SystemClock.elapsedRealtime();
// Handle button clicks
if (v == R.id.imageView2) {
// Do ur stuff.
}
else if (v == R.id.imageView2) {
// Do ur stuff.
}
...
}
.
.`.
You should put "public synchronized" void, so it will prevent to be called in parallel. If you must override, then put a synchronized object in the method so it will do the same.