My Activity:
class PlayerDetails : AppCompatActivity(), View.OnClickListener {
private lateinit var binding: ActivityPlayerDetailsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_player_details)
}
I'm trying to understand how the data binding process works, this is how I understand it:
private lateinit var binding: ActivityPlayerDetailsBinding
instantiates the ViewDataBinding object.
binding = DataBindingUtil.setContentView(this,
R.layout.activity_player_details) is in 2 parts:
DataBindingUtil.setContentView(this,
R.layout.activity_player_details) sets the content view to the given layout
It then returns the binding object to the binding variable (binding = ...) which can then be used to access views in the layout.
Is this an accurate way of describing how the code is working? I found the source code for DataBindingUtil.java hard to understand. Mostly because setContentView() is being called even though it appears to be assigned instead binding = ....
DataBindingUtil.setContentView(this, R.layout.activity_player_datails) do almost same thing to return binding object. Although DataBindingUtils.setContentView call activity.setContentView before returning.
Instead, I usually override setContentView to make sure assign binding object into variables and sets content to the given layout.
override fun setContentView(layoutResID: Int) {
binding = DataBindingUtil.inflate(LayoutInflater.from(context), layoutResID, null, false)
super.setContentView(mBinding.root)
}
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
What I want is to do this (which is in Java):
public class MainActivity exteds AppCompatActivity{
ImageView logo;
#override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
logo = findViewById(R.id.logo);
}
when taking it to Kotlin I get an error:
Please could you tell me which of the options to keep so that the error does not appear?
and what class is TODO()?
Use lateinit which will allow you to initialize the property later on.
lateinit var logo: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
logo = findViewById(R.id.logo)
}
You can initialize as null and later use it
var logo: ImageView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.simple_layout)
logo = findViewById(R.id.imageView)
}
You can't reassign a val, so I would suggest you make it a var. Since you also want to initialize it later, you will need to declare a lateinit var, i.e lateinit var logo: ImageView or you can initialize it as null i.e var logo: ImageView? = null and then later you can reassign as initented logo = findViewById(R.id.logo)
If you want to use this logo = findViewById(R.id.logo); in kotlin then you don't need to declare any extra variable you can simply use logo.setOnClickListener { } or anything similar kotlin will directly import it from your xml.(Note :- here logo is your imageView id).
All though if you want to declare any variable and initialize it later on then you can use lateinit var logo: ImageView or simply and nullable variable like var logo: ImageView? = null and initialize it later on.
There are a lot of blogs and S.O Questions on google where you can find more about lateinit and nullable variables. Here is one of the S.O question you can refer.
I have a module, UserModule that takes in a string and provides a user object.
UserModule:
#Module
class UserModule(val name: String) {
#Provides
fun provideUser() : User = User(name = name)
}
And a ViewModelComponent that has UserModule as one of its components
ViewModelComponent:
#Singleton
#Component(modules = [UserModule::class])
interface ViewModelComponent {
fun inject(activity: MainActivity)
}
Normally I would provide the component in my application like this:
class MainApplication : Application() {
lateinit var component: AppComponent
override fun onCreate() {
super.onCreate()
component = DaggerAppComponent.builder()
.userModule(UserModule("Name"))
.build()
}
}
And reference it in my activity like this:
class MainActivity : AppCompatActivity() {
#Inject lateinit var user: User
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as MainApplication).component.inject(this)
}
However, this assumes that the value of UserModule name is known at runtime and is hardcoded into MainApplication, however, in reality, the value of name is obtained from MainActivity.
The only solution I can think of is to build the dependency graph in MainActivity so I am able to pass name like this:
class MainActivity : AppCompatActivity() {
#Inject lateinit var user: User
val newUserName = "NewName"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
component = DaggerAppComponent.builder()
.userModule(UserModule(newUserName))
.build()
component.inject(this)
}
This solution seems very unefficient but it is the only way I can inject dynamic parameters in Dagger 2. Is there a better way to achieve this
I just ran into this issue aswell, first solution that I thought of was to create some "static holder" class that would hold dynamic parameters and it would be developers responsibility to update this holder at the right time - before injection happens I guess. I didnt actually try it though and it doesnt seem to be too clean either.
Anway after a bit of googling I found this article: https://proandroiddev.com/dagger-2-module-parameters-7c820d944a which seems to be more "dagger oriented" solution, didnt try that either, but it's something you can start with.
I would only comment this but I dont have required reputation...
I need to do some input validation for my TextInputEditText that is wrapped with TextInputLayout.
I'd like errors to appear below the line if the input is done in wrong format.
All the logic is done in the viewmodel instead of the view(fragment or activity). But I can't seem to access the view through viewmodel, for instance:
textinputlayout.setError("error") doesn't work in the viewmodel
and layout.findViewbyId(layoutId) doesn't work in the viewmodel either.
Any idea?
used below code to set error in TextInputLayout..
class SpinerActivity :AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.spiner)
setEdittext()
}
private fun setEdittext() {
var textError:TextInputLayout=findViewById(R.id.amEt1)
textError.error="Please Enter Name"
}
}
I am trying to intercept every view widget that is being inflated in my activity and override the setText functionality of that view if it exists.
So if a TextView has a android:text="bla" in the XML layout i want to be able to intercept that and maybe add a ** at the end of all texts being set from the xml.
One way that seems to be close to what i need is to use a Custom Layout inflator.
LayoutInflaterCompat.setFactory(layoutInflator, InflatorOnSteriods(this))
and the in my InflatorOnSteriods to override onCreateView and then intercept all views there.. this approach doesn't seem to work at all. onCreateView is never called.
I tried also to use cloneInContext
LayoutInflaterCompat.setFactory(layoutInflater.cloneInContext(this), InflatorOnSteriods(this))
But no luck as well, maybe my approach is totally wrong i am also open to a different way where i can intercept all views being presented and to be specific set a certain attribute on that view. It is really important to make sure that i will be the last one changing that view and make sure the system respects my changes and wont override them later.
Update:
Although i don't think its relevant; Code of InflatorOnSteroids.kt
class InflatorOnSteriods(val appCompatActivity: AppCompatActivity) : LayoutInflaterFactory {
override fun onCreateView(parent: View, name: String, context: Context, attrs: AttributeSet): View {
var result: View
if (TextUtils.equals(name, "DebugDrawerLayout")) {
result = ImageView(context, attrs)
}
result = appCompatActivity.onCreateView(name, context, attrs)
if (result == null) {
// Get themed views from app compat
result = appCompatActivity.delegate.createView(parent, name, context, attrs)
}
return result
}
}
After some time troubleshooting my solution i finally managed to achieve what i wanted with the inflator factory solution.
First i create an abstract activity that has a custom inflator set to it.
abstract class SteroidsActivity : AppCompatActivity() {
var mInflater: LayoutInflater? = null
abstract fun getActivityLayout(): Int
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mInflater = layoutInflater?.cloneInContext(this)
LayoutInflaterCompat.setFactory(mInflater, InflaterOnSteroids(this))
setContentView(getActivityLayout())
}
override fun getLayoutInflater(): LayoutInflater? {
return mInflater
}
override fun getSystemService(name: String): Any? {
if (name == LAYOUT_INFLATER_SERVICE) {
if (mInflater == null) {
mInflater = super.getSystemService(name) as LayoutInflater
}
return mInflater
}
return super.getSystemService(name)
}
}
Second thing that you need to do is create your custom inflator factory
class InflaterOnSteroids(appCompatActivity1: AppCompatActivity) : LayoutInflaterFactory {
override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? {
//Do stuff here and return a view
return null
}
}
The problem with my code was that it was always crashing with a weird error that i couldn't troubleshoot until i realised that i need to add a ? after View since i'm using kotlin and parent view can be null :)
Happy programming
Good reference can be found here
I have a Java Android application which I want to change to Scala. I have many fragments and I want to know what is the best way to do this in Scala.
This is my Java fragment class MyFragment:
public class MyFragment extends Fragment {
private WebView myWebView;
private TextView myTextView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View myView = inflater.inflate(R.layout.my_view, container, false);
myWebView = (WebView) myView.findViewById(R.id.my_webview);
myWebView.loadUrl("http://www.google.com/");
myTextView = (TextView) myView.findViewById(R.id.my_textview);
myTextView.setText("Google.com");
return myView;
}
}
I always have this base structure: some private UI elements which I instantiate in onCreateView, do some things and return the view (Not shown here: in other on* methods I also do some actions with the UI elements).
I found some articles which do a lazy val like described here: http://blog.andresteingress.com/2011/09/20/programming-android-with-scala/
But in my case, this does not work, because I have fragments and not activities. First I have to inflate the main View myView and then I can get the UI elements of it.
What is the best way to do this in Scala?
--- UPDATE ---
My Scala code looks like this at the moment:
class MyFragment extends Fragment {
private var myTextView: TextView = null
override def onCreateView(inflater: LayoutInflater,
container: ViewGroup, savedInstanceState: Bundle): View = {
val myView = inflater.inflate(R.layout.my_view, container, false)
val myWebView = myView.findViewById(R.id.my_webview).asInstanceOf[WebView]
myWebView.loadUrl("http://www.google.com/")
myTextView = myView.findViewById(R.id.my_textview).asInstanceOf[TextView]
myTextView.setText("Google.com")
myView
}
}
So, what can I improve here? myTextView is a private var because I have to access it several methods in this Fragment. It seems I can not do the stuff explained here: http://blog.andresteingress.com/2011/09/20/programming-android-with-scala/ with lazy val TypedActivity and the implicit conversion of OnClickListener, because I use fragments. So how can I get rid of boilerplate code with .asInstanceOf[T] and make it more Scala like?
Based on your updated code I can only make some suggestion to be more "scala-ish"
Use Option instead of null for your members
private var myWebView: Option[WebView] = None
private var myTextView: Option[TextView] = None
To avoid explicit casting of your views in the code, you need to move it elsewhere, but you cant' get rid of it, because the original android API doesn't give you any clue as to the runtime or compiletime type of the returned objects. To overcome this issue, the post you mentioned uses custom-made typed resources and a trait that handles the types from these.
case class TypedResource[T](id: Int)
object TR {
object id {
val my_webview = TypedResource[TextView](R.id.my_webview)
val my_textview = TypedResource[WebView](R.id.my_textview)
//... you must put here all your typed views referenced by id
}
}
trait TypedViewHolder {
def view: View
//the method explicitly casts to the needed resource type based on the argument
def findView[T](tr: TypedResource[T]): T = view.findViewById(tr.id).asInstanceOf[T]
}
object TypedResource {
//this will implicitly convert your views to a corresponding TypedViewHolder
//this lets you avoid explicit type cast to get your view
implicit def view2typed(v: View): TypedViewHolder = new TypedViewHolder { def view = v }
}
Now we can use the above code
val myView = inflater.inflate(R.layout.my_view, container, false)
val myWebView = myView.findView(TR.id.my_webview)
myWebView.loadUrl("http://www.google.com/")
val myTextView = myView.findView(TR.id.my_textview)
myTextView.setText("Google.com")
Putting both things together
class MyFragment extends Fragment {
private var myWebView: Option[WebView] = None
private var myTextView: Option[TextView] = None
override def onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedInstanceState: Bundle): View = {
//imports the implicit conversion
import TypedResource._
val myView = inflater.inflate(R.layout.my_view, container, false)
myWebView = Some(myView.findView(TR.id.my_webview))
//now we're using options, so we must call methods on the inner value
//we can use Option.map(...) to do it [see http://www.scala-lang.org/api/current/index.html#scala.Option]
myWebView.map(_.loadUrl("http://www.google.com/"))
myTextView = Some(myView.findView(TR.id.my_textview))
//same as above
myTextView.map(_.setText("Google.com"))
myView
}
}
I hope this helps you out. I'm no expert with android so I can only get so far.
Why don't you simply write the fragment in Scala without bothering about "the best way to do this in Scala"? There might be none.
I'd start with removing public from the class definition and including the other goodies - TypedActivity from the article - in the activity. Then, set up the development environment - the IDE - and run the application. If it works, you're done (with the very first step in the migration). I don't think you need lazy val's from the very beginning.
Do small steps so the migration's easier.