I'm a beginner in MVVM and I tried this tutorial. Created a lot of files and pasted the codes but my views aren't recognized. My QuotesActivity.kt file:
package my.mvvm.ui.quotes
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import my.mvvm.R
import my.mvvm.data.Quote
import my.mvvm.utilities.InjectorUtils
class QuotesActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_quotes)
initializeUi()
}
private fun initializeUi() {
// Get the QuotesViewModelFactory with all of it's dependencies constructed
val factory = InjectorUtils.provideQuotesViewModelFactory()
// Use ViewModelProviders class to create / get already created QuotesViewModel
// for this view (activity)
val viewModel = ViewModelProviders.of(this, factory)
.get(QuotesViewModel::class.java)
// Observing LiveData from the QuotesViewModel which in turn observes
// LiveData from the repository, which observes LiveData from the DAO ☺
viewModel.getQuotes().observe(this, Observer { quotes ->
val stringBuilder = StringBuilder()
quotes.forEach { quote ->
stringBuilder.append("$quote\n\n")
}
textView_quotes.text = stringBuilder.toString()
})
// When button is clicked, instantiate a Quote and add it to DB through the ViewModel
button_add_quote.setOnClickListener {
val quote = Quote(editText_quote.text.toString(), editText_author.text.toString())
viewModel.addQuote(quote)
editText_quote.setText("")
binding.editText_author.setText("")
}
}
}
Any ideas why this happens? Already tried with two examples and I had the same issue. Thanks.
Edit:
import com.resocoder.mvvmbasicstut.data.Quote
import com.resocoder.mvvmbasicstut.databinding.ActivityQuotesBinding
import com.resocoder.mvvmbasicstut.utilities.InjectorUtils
//import kotlinx.android.synthetic.main.activity_quotes.*
class QuotesActivity : AppCompatActivity() {
lateinit var binding: ActivityQuotesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityQuotesBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.quotes.QuotesActivity">
<TextView
android:id="#+id/textView_quotes"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintHeight_percent="0.55"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:scrollbars="vertical"
android:textAppearance="#style/TextAppearance.AppCompat.Large"
tools:text="I like pineapples. - Thomas Jefferson"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<EditText
android:id="#+id/editText_quote"
android:layout_width="0dp"
app:layout_constraintWidth_percent="0.7"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:hint="Quote"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView_quotes"
app:layout_constraintVertical_bias="0.0" />
<EditText
android:id="#+id/editText_author"
android:layout_width="0dp"
app:layout_constraintWidth_percent="0.7"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:hint="Author"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/editText_quote"
app:layout_constraintVertical_bias="0.0" />
<Button
android:id="#+id/button_add_quote"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:backgroundTint="?colorAccent"
android:text="Add Quote"
android:textColor="#android:color/white"
app:layout_constraintBottom_toBottomOf="#+id/editText_author"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="#+id/editText_quote"
app:layout_constraintTop_toTopOf="#+id/editText_quote"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintWidth_percent="0.25" />
</androidx.constraintlayout.widget.ConstraintLayout>
I just edit project from github. You can find it here
The answer to 'Look at my edited question' in Laba Diena's 'Edit:' above of wanting to use View Binding instead of the Kotlin Android Extension with synthetic view bindings is fairly straight forward. There are only two project files that need changing, namely:
(1) 'build.gradle (Module)' and
(2) 'QuotesActivity.kt'
Note: The other project files from the MVVM tutorial can be used 'as is'.
Just make sure that your own 'package name' is at the top of these files.
Project Structure:
(1) build.gradle file:
a) Make this change to the 'plugins' section:
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-android-extensions' // Delete this line
}
b) Add this entry to the 'android' section:
android {
...
buildFeatures {
viewBinding true
}
}
c) Add this lifecycle option to the 'dependencies' section:
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
(2) QuotesActivity.kt:
Make the necessary changes to the 'QuotesActivity.kt' file as outlined below:
IMPORTANT: When this project is built, it generates a Java file with the necessary databindings for this activity file. It will contain new names that you will have to use with the 'binding' variable in place of the xml attribute names that were previously used. For example, 'button_add_quote' now becomes 'buttonAddQuote', etc. See excerpt below showing the other new names:
public final Button buttonAddQuote; // button_add_quote
public final EditText editTextAuthor; // editText_author
public final EditText editTextQuote; // editText_quote
public final TextView textViewQuotes; // textView_quotes
All these new names can be found in a file called 'ActivityQuotesBinding.java' which is located in the project folder:
'app/build/generated/data_binding_base_class_source_out/debug/out/com/example/mvvmcrashcourse/databinding/'
In the above path, the package name used for this particular project is "com.example.mvvmcrashcourse". Substitute it with your package name.
Finally, this solution works and was tested with Android Studio Bumblebee. If you have problems with build errors, try the following menu options:
Build -> Clean Project
Build -> Rebuild Project
File -> Invalidate Caches
Related
ok, brand new to android studio and kotlin (not programming in general)
installed Android Studio, set up a couple of hardware profiles (and enabled dev mode).
Here is my issue:
Simple app, 'every time you click the button, the number above doubles.'
in the first_fragment.xml file, I rename the ID to 'textDisplayedValue'
in the MainActivity.kt file, I create 2 variables originalValue and newValue.
When I try to set the originalValue to the value in textDisplayedValue, I get an 'Unresolved reference' error
Code
Log
In addition, when I launch the debugger, the 'ok' button is disabled. I assume that is due to the code errors so correct me if I am wrong.
Thanks in advance for the help.
Layout File
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstFragment">
<TextView
android:id="#+id/textDisplayedValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
android:textAppearance="#style/TextAppearance.AppCompat.Large"
app:layout_constraintBottom_toTopOf="#id/button_first"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/button_first"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/textDisplayedValue" />
</androidx.constraintlayout.widget.ConstraintLayout>
binding.fab.setOnClickListener { view ->
val originalValue = 1
val newValue = originalValue * 2
textDisplayedValue = newValue.toString()
Snackbar.make(view, "Value $originalValue changed to $newValue", Snackbar.LENGTH_LONG).show()
}
Try this
Comment the not working code
From the toolbar select
A. Build -> clean project
B. After that Build -> rebuild project
Uncomment the not working code and do this
val originalValue = binding.textDisplayedValue. text() .toString().toLong()
val newValue = originalValue * 2
I'm struggling with very common problem, I think.
I've created a button in xml file and tagged it with ID. Then I wanted to make onClickListener in MainActivity.kt. But when I'm typing button's ID, it's marked red and it seems like Android Studio doesn't recognise it. I've tried cleaning and rebuilding project, but the problem still exist. Invalidate Caches/Restart didn't help as well.
Here's XML Code
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/backgroundColor"
android:fadingEdge="vertical"
android:fadingEdgeLength="80dp"
android:gravity="center_horizontal"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity"
tools:viewBindingIgnore="true">
<Button
android:id="#+id/btnDateBicker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:backgroundTint="#3D446C"
android:text="Select Date"
android:textSize="25sp"
android:textStyle="bold" />
</LinearLayout>
And here's kotlin code
package com.example.ageinminutes
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mybtn = findViewById<btnDateBicker>()
}
}
If you simply want to fetch by id, use findViewById method.
val myBtn: Button = findViewById(R.id.btnDateBicker)
or
val myBtn = findViewById<Button>(R.id.btnDateBicker)
Another way to play with views in the kotlin file:
Try with synthetic binding, like just start writing few words of XML id in your kotlin file and android studio will attach that view for you. [Deprecated after Android 11]
Try with view binding or data binding, you just have to enable these settings in build.gradle file. [More Robust way]
Try this:
val mybtn = findViewById<Button>(R.id.btnDateBicker)
having a problem with Navigation Advanced Sample provided by Google developers.
Main problem is that in regular case senario for Hilt dependency injection we can simply:
Create Custom FragmentFactory
Create Custom NavHostFragment
Asigne it to FragmentContainerView by using android:name="com.package.CustomNavHostFragment"
But how I can do it by using this Navigation Advanced Sample ?
Because now on Activity recreate I'm getting typical HILT error at package.MainActivity.onCreate(MainActivity.kt:23)
EDIT:
More about the problem. By this provided simple we should use
NavigationExtension.kt and setup everything by using BottomNavigationView.setupWithNavController
And problem is that we need to use the default NavHostFragment in order to create a container for each navigation graph.
Is it possible somehow to use custom NavHostFragment? If yes, how can I overite that NavHostFragment.onCreate() mechanism?
I'm talking about this line in NavigationExtension.kt class
If I understood your problem correctly, you want to create a custom NavHostFragment? Yes, that is possible but you don't need to overwrite NavHostFragment.onCreate(). Create a custom NavHostFragment with the following steps: First, create a MainFragmentFactory
MainFragmentFactory
class MainFragmentFactory #Inject constructor(
//... your dependencies
) : FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment = when(className) {
MyFragment::class.java.name -> MyFragment(//.. your dependencies)
MySecondFragment::class.java.name -> MySecondFragment(/...)
// other fragments
else -> super.instantiate(classLoader, className)
}
}
Then you need to attach your fragmentFractory to your custom MainNavHostFragment
MainNavHostFragment
#AndroidEntryPoint
class MainNavHostFragment : NavHostFragment() {
#Inject
lateinit var mainFragmentFactory: MainFragmentFactory
override fun onAttach(context: Context) {
super.onAttach(context)
childFragmentManager.fragmentFactory = mainFragmentFactory
}
}
Now you need to add your custom NavHostFragment to your activity_main.xml
Activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".framework.ui.view.MainActivity">
<fragment
android:id="#+id/fragment_container"
<!-- Add path to your custom NavHostFragment here -->
android:name="com.example.app.framework.ui.view.utils.MainNavHostFragment"
android:layout_width="match_parent"
android:layout_height="#dimen/wrapContent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#+id/bottomNavigationView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
<!-- Your NavGraph -->
app:navGraph="#navigation/nav_main" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemHorizontalTranslationEnabled="false"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/bottom_nav_menu_logged_out" />
</androidx.constraintlayout.widget.ConstraintLayout>
Dagger-Hilt will now create all your fragments and instantiate it.
So I completed the first lesson of the Kotlin Course by Google on Udacity and after that I went on to make my own changes to the app that make them look better (just to learn, I don't care about the app). I made some changes like adding dark mode and customizing the UI.
I want the dark mode menu to work properly, like when I click on light mode it should recreate the activity with light mode enabled and should also check the light mode MenutItem. I also want the app to remember the settings so that if I choose Follow System during one session of the app and then close the app, it should keep following the system even after opening the app. I figured this should be done with SharedPreferences but I don't know how to properly do it. As you can see in the video attached below, when I click on any of the dark mode settings options, they don't automatically change the settings, I need to restart the app and only then do I see any changes in the activity that matches to whatever option I selected before closing the app.
Note: I want to mention that I am not very experienced with Android Development or Kotlin, I only started learning 2 months ago. I also want to mention that I DO NOT care about the app itself but I am rather using this app to learn how to get things done. I don't care if my app gets every feature I
would want it to have if I don't also learn how to do those things. It's not the app but the knowledge that I am getting from it that I care about, this is to say that please if you have an answer, do explain how that works.
This video shows the behavior of the app: https://drive.google.com/file/d/1SnN0r351OGF4xQNCtI1wtgouSFVROKzg/view?usp=sharing
I added a toolbar to my layout file of the main (and the only) activity. Here's the xml code for the layout resource:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:id="#+id/diceRollerActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/activity_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:title="#string/app_name" />
</com.google.android.material.appbar.AppBarLayout>
<ImageView
android:id="#+id/dice_image"
android:layout_width="275dp"
android:layout_height="335dp"
android:contentDescription="#+id/diceImage_imageView_description"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.395"
app:srcCompat="#drawable/empty_dice" />
<Button
android:id="#+id/roll_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="#dimen/Container_Button_Padding"
android:text="#string/roll"
android:textAlignment="center"
android:textAllCaps="false"
android:textColor="#color/white"
android:textSize="25sp"
app:backgroundTint="#color/bold_button_color"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/dice_image"
app:layout_constraintVertical_bias="0.32"
app:rippleColor="#color/button_ripple" />
</androidx.constraintlayout.widget.ConstraintLayout>
and here is my the Kotlin code for the same activity:
package com.example.diceroller
import android.content.Context
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.*
import androidx.appcompat.app.AppCompatDelegate
class MainActivity : AppCompatActivity() {
lateinit var diceImage: ImageView
lateinit var sharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.activity_toolbar))
val rollButton: Button = findViewById(R.id.roll_button)
diceImage = findViewById(R.id.dice_image)
rollButton.setOnClickListener {
rollDice()
}
sharedPreferences = getSharedPreferences(getString(R.string.app_name), Context.MODE_PRIVATE)
when (sharedPreferences.getString("Night_Mode_State", getString(R.string.follow_system))) {
getString(R.string.follow_system) -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
getString(R.string.light_mode) -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
getString(R.string.dark_mode) -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater: MenuInflater = menuInflater
inflater.inflate(R.menu.actionbar, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.follow_system -> sharedPreferences.edit().putString("Night_Mode_State", getString(R.string.follow_system)).apply()
R.id.light_mode -> sharedPreferences.edit().putString("Night_Mode_State", getString(R.string.light_mode)).apply()
R.id.dark_mode -> sharedPreferences.edit().putString("Night_Mode_State", getString(R.string.dark_mode)).apply()
}
return true
}
private fun rollDice() {
when ((1..6).random()) {
1 -> diceImage.setImageResource(R.drawable.dice_1)
2 -> diceImage.setImageResource(R.drawable.dice_2)
3 -> diceImage.setImageResource(R.drawable.dice_3)
4 -> diceImage.setImageResource(R.drawable.dice_4)
5 -> diceImage.setImageResource(R.drawable.dice_5)
else -> diceImage.setImageResource(R.drawable.dice_6)
}
}
}
This gist includes other files that I think are required to answer this question properly, namely: The menu resource file, the colors resource file and the styles resource file: https://gist.github.com/sbeve72/36a71919f1f965c5dcd466db1e099b4f.js"
The Code A and Code B are from the project https://github.com/android/databinding-samples.
The Code B display an icon based fun popularityIcon(view: ImageView, popularity: Popularity) and works well.
I find that project can still work well even if I rename #BindingAdapter("app:popularityIcon") to #BindingAdapter("myok:popularityIcon"), just like Code C, why?
Code A
object BindingAdapters {
#BindingAdapter("app:popularityIcon")
#JvmStatic fun popularityIcon(view: ImageView, popularity: Popularity) {
val color = getAssociatedColor(popularity, view.context)
ImageViewCompat.setImageTintList(view, ColorStateList.valueOf(color))
view.setImageDrawable(getDrawablePopularity(popularity, view.context))
}
...
}
Code B
<ImageView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginTop="24dp"
android:contentDescription="#string/profile_avatar_cd"
android:minHeight="48dp"
android:minWidth="48dp"
app:layout_constraintBottom_toTopOf="#+id/likes_label"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed"
app:popularityIcon="#{viewmodel.popularity}"/>
Code C
object BindingAdapters {
#BindingAdapter("myok:popularityIcon")
#JvmStatic fun popularityIcon(view: ImageView, popularity: Popularity) {
val color = getAssociatedColor(popularity, view.context)
ImageViewCompat.setImageTintList(view, ColorStateList.valueOf(color))
view.setImageDrawable(getDrawablePopularity(popularity, view.context))
}
...
}
Databinding ignores namespaces. So it removes app: or myok: or anything else. Also, if you put both adapters with the same name but different namespaces, you would get an error telling you that there are more than one adapter for popularityIcon.
You can check the docs for more information.
Note: The Data Binding Library ignores custom namespaces for matching purposes.
you need to update your namespace in your XML were you using this binding.
like below
xmlns:myok="http://schemas.android.com/apk/res-auto"
check below code
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myok="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
</androidx.constraintlayout.widget.ConstraintLayout>