How can i refrence variable inside outer class (kotlin) - android

How can i reference the SongName variable in my other object? I am building my first app with Kotlin so I am really beginner.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
SpotifyService.connect(this) {
spotifyAppRemote?.let {
it.playerApi.subscribeToPlayerState().setEventCallback {
val track: Track = it.track
Log.d("MainActivity", track.name + " by " + track.artist.name)
ALabel.text = track.name
Blabel.text = track.artist.name
var SongName = track.name
var SongArtist = track.artist.name
}
}
}
GeniusApi.PrintSomething()
}
}``` ```object GeniusApi {
val BASE_URL = "https://api.genius.com"
val SEARCH_URL = BASE_URL + "/search"
var DATA = SongName#MainActivity
fun PrintSomething() {
Log.e("GeniusApi", DATA)
}
}```

On your Kotlin file you will need to place any functions or variables inside a
Companion Object {
lateinit var myobject
}
And then reference it like
TheObject.Companion.myobject

Related

How to split viewmodel or build master viewmodel with small viewmodels?

I have one pretty big complicated ViewModel and I want to split it or build it with few smaller ViewModels.
Below I want to show how I make my ViewModels in general (please do not laugh, this is my first Android ViewModel). I'm not using DataBinding, just ViewBinding.
class AssignUserTagToInventoryItemViewModel() : ViewModel() {
private val UserTag = "MyApp" + this.javaClass.simpleName
init {
Log.d(UserTag, "Class init called")
loadInventoryItems()
loadRandomUserTags() // todo: replace with real implementation
}
private var allItems = ArrayList<InventoryItemDto?>()
//<editor-fold desc="FilterByName">
private val _filterByName = MutableLiveData<String>("")
val filterByName: LiveData<String> get() = _filterByName
fun setFilterByName(t : String) { _filterByName.value = t; applyFilters();}
//</editor-fold>
//<editor-fold desc="FilterByAssignedToMe">
private val _filterByAssignedToMe = MutableLiveData<Boolean>(false)
val filterByAssignedToMe: LiveData<Boolean> get() = _filterByAssignedToMe
fun setFilterByAssignedToMe(t : Boolean) { _filterByAssignedToMe.value = t; applyFilters(); }
//</editor-fold>
//<editor-fold desc="SelectedInventoryItem">
private val _selectedInventoryItem = MutableLiveData<InventoryItemDto?>(null)
fun getSelectedInventoryItem() : LiveData<InventoryItemDto?> = _selectedInventoryItem
fun setSelectedInventoryItem(itemDto: InventoryItemDto?) {
_selectedInventoryItem.value = itemDto
selectedItemOrUserTagChanged()
}
//</editor-fold>
// <editor-fold desc="FilteredItems">
val _displayedItems = MutableLiveData<ArrayList<InventoryItemDto?>>(ArrayList())
val displayedItems: LiveData<ArrayList<InventoryItemDto?>> get() = _displayedItems
// </editor-fold>
// <editor-fold desc="ItemsListError">
val _itemsListError = MutableLiveData<String>("")
val itemsListError :LiveData<String> get() = _itemsListError
fun setItemsListError(s : String) { _itemsListError.value = s }
// </editor-fold>
//<editor-fold desc="UserTag list">
val _UserTags = MutableLiveData<ArrayList<UserTag>>(ArrayList())
val UserTags : LiveData<ArrayList<UserTag>> get() = _UserTags
fun setUserTags(a : ArrayList<UserTag>) { _UserTags.value = a }
//</editor-fold>
//<editor-fold desc="SelectedUserTagItem">
private val _selectedUserTag = MutableLiveData<UserTag?>(null)
fun getSelectedUserTag() : LiveData<UserTag?> = _selectedUserTag
fun setSelectedUserTag(UserTag : UserTag?) {
_selectedUserTag.value = UserTag
selectedItemOrUserTagChanged()
}
//</editor-fold>
//<editor-fold desc="CanSubmit">
private val _canSubmit = MutableLiveData<Boolean>(false)
val canSubmit: LiveData<Boolean> get() = _canSubmit
//</editor-fold>
private fun selectedItemOrUserTagChanged() {
_canSubmit.value = true
}
private fun loadInventoryItems(){
Log.d(UserTag, "Loading inventory items...")
viewModelScope.launch {
try {
val apiResponse = ApiResponse(ApiAdapter.apiClient.findAllInventoryItems())
if (apiResponse.code == 200 && apiResponse.body != null) {
allItems = apiResponse.body
applyFilters()
Log.d(UserTag, "Loading inventory items done.")
}
else {
setItemsListError(apiResponse.code.toString())
Log.d(UserTag, "Loading inventory items error.")
}
} catch (t : Throwable) {
setItemsListError(t.message.toString())
}
}
}
private fun applyFilters(){
Log.d(UserTag, "ViewModel apply filters called. Current name filter: ${filterByName.value}")
val tempResults = ArrayList<InventoryItemDto?>()
val nameFilterLowercase = filterByName.value.toString().lowercase()
if (!filterByName.value.isNullOrEmpty()) {
for (item in allItems) {
val itemNameLowercase = item?.name?.lowercase()?:""
if (itemNameLowercase.contains(nameFilterLowercase))
tempResults.add(item)
}
_displayedItems.value = tempResults
} else {
_displayedItems.value = allItems
}
}
private fun loadRandomUserTags(){
val temp = ArrayList<UserTag>()
for (i in 1..50){
val epc = getRandomHexString(24).uppercase()
val UserTag = UserTag(epc, 0, "0")
temp.add(UserTag)
}
viewModelScope.launch {
delay(100)
_UserTags.value = temp
}
}
private fun getRandomHexString(numchars: Int): String {
val r = Random()
val sb = StringBuffer()
while (sb.length < numchars) {
sb.append(Integer.toHexString(r.nextInt()))
}
return sb.toString().substring(0, numchars)
}
}
Simply create multiple view models according to the task they are performing.
There are several problems here :
Your ViewModel name is too long
You can create an object of the getRandomHexString and this way you can use it inside any other classes or ViewModels you may need in future. It also saves space inside ViewModel.
Learn about the clean architecture and follow its practices. Here, you can create a separate view model or helper class for filtering your results. If you create another view model, you can simply retrieve results from your current view model to the activity and call filter view model inside your activity. This way you can separate code blocks according to the role they play or the function they perform.

Realm Kotlin - Delete realm object

https://www.mongodb.com/docs/realm/sdk/kotlin/realm-database/delete/delete-all-objects-of-a-type/
I am learning new kotlin-realm in my project. But i dont know how to delete objects. It keep showing error Caused by: io.realm.internal.interop.RealmCoreNotInATransactionException: [5]: Must be in a write transaction
this is the code :
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val realm by lazy { (application as CustomApplication).realm }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
realm.writeBlocking {
copyToRealm(Buku(
name = "Perjuangan Menjual Baju"
))
copyToRealm(Buku(
name = "Perang Saudara"
))
}
val buku = realm.query<Buku>("name BEGINSWITH $0", "pera")
Log.i("AOEU", "buku = $buku")
CoroutineScope(Dispatchers.Main).launch {
val query = realm.query<Buku>().find()
realm.write {
delete(query)
}
}
}
}
After days of finding solution. I just realized that the only mistake in my code is
val query = realm.query().find()
Which is should be replaced with
val query = this.query().find()

Data Binding not working for textView while using some function

First I have declared the variables in the layout file
<data>
<variable
name="signUpViewModel"
type="ac.connect.ui.signup.SignUpViewModel" />
</data>
Now I'm trying to use a function of StringExtension class which takes string value as parameter and to set the result to textView
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="20dp"
android:textStyle="bold"
android:background="#C1BDBD"
android:textColor="#3D3C3C"
app:capitalizeFirstLetter="#{signUpViewModel.name}" />
capitalize First Letter function
fun capitalizeFirstLetter(value: String): String {
var ans = ""
val words = value.split(" ")
words.forEach {
ans += it.capitalize() + " "
}
return ans
}
ViewModel
class SignUpViewModel(private val setProfileUseCase: SetProfileUseCase) :
ViewModel() {
private val _profile = MutableLiveData<ProfileModel>()
val profile: LiveData<ProfileModel>
get() = _profile
private val _name = MutableLiveData<String>()
val name: LiveData<String>
get() = _name
fun setName(name: Editable) {
_name.value = name.toString()
}
fun setProfileData() {
viewModelScope.launch {
val profile = ProfileModel(
name = "kamal nayan",
branch = "CSE",
gender = "Male",
mobileNumber = "+91-73555555517",
rollNo = "GCS/345353",
uid = "ghafagaraggGGG"
)
val response = setProfileUseCase.invoke(profile)
_profile.value = profile
response.successOrError(::handleProductDetailsSuccess, ::handleSignUpFailure)
}
}
private fun handleProductDetailsSuccess(response: Boolean) {
_name.value = "User Data Uploaded Successfully"
}
private fun handleSignUpFailure(failure: Failure, error: ErrorResponse?) {
Timber.log(Log.ERROR, error?.message)
}
}
Fragment Code:
class SignUpFragment : Fragment(R.layout.fragment_sign_up) {
private val viewModel: SignUpViewModel by viewModel()
private var binding by autoCleared<FragmentSignUpBinding>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentSignUpBinding.bind(view)
binding.signUpViewModel = viewModel
binding.lifecycleOwner = this
binding.signUp.setOnClickListener {
viewModel.setProfileData()
}
}
companion object {
fun newInstance(): SignUpFragment {
return SignUpFragment()
}
}
}
The output is blank , like no text in the textView
Kindly help me with this, Thanks in advance.
I think the problem is you are not setting the value of the variable stringExtensions when setting up your DataBinding
import ac.connect.utils.StringExtensions
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentSignUpBinding.bind(view)
binding.signUpViewModel = viewModel
// binding.stringExtensions = StringExtensions() // If it is a class
// binding.stringExtensions = StringExtensions // If it is an object
binding.lifecycleOwner = this
binding.signUp.setOnClickListener {
viewModel.setProfileData()
}
}
If I understand right you need make BindingAdapter. So you need rewrite your 'capitalizeFirstLetter' function as below.
#BindingAdapter("app:capitalizeFirstLetter")
#JvmStatic fun capitalizeFirstLetter(textView: TextView, value: String){
var ans = ""
val words = value.split(" ")
words.forEach {
ans += it.capitalize() + " "
}
textView.setText(ans)
}
Also I didn't see where you call setName function. Are you sure that _name.value is not empty?

Android Coroutines block UI click listener

I'm using MVVM as architecture, also the repository pattern. I have a Web service, a room database also. Using coroutines block any button I click.
There's a list/detail implemented with a fragment and an activity respectively.
I can figure out what's wrong in the way I implemented the coroutines and Viewmodel.
class BuySharedViewModel(application: Application) : AndroidViewModel(application) {
private val repository: BuyRepository
var allBuys: LiveData<List<Buy>>
init {
val buyDao = KunukRoomDatabase.getDatabase(application, viewModelScope).buyDao()
val buyRemote = BuyRemote()
repository = BuyRepository.getInstance(buyDao , buyRemote)
//Use async because it return a result
viewModelScope.launch { getAllBuys() }
allBuys = buyDao.loadAllBuys()
}
private suspend fun getAllBuys() {
repository.getBuys()
}
}
Here's is the Repository, it take data from web service and add it to the room database, while ViewModel get's data from room database.
class BuyRepository (private val buyDao: BuyDao, private val buyRemote: BuyRemote) {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Default + job)
companion object {
//For singleton instantiation
#Volatile private var instance: BuyRepository? = null
fun getInstance(buyDao: BuyDao, buyRemote: BuyRemote) =
instance ?: synchronized(this) {
instance ?: BuyRepository(buyDao, buyRemote)
.also { instance = it}
}
}
suspend fun getBuys(){
refresh()
}
private suspend fun refresh(){
try {
val list = scope.async { buyRemote.loadBuys() }
list.await().forEach { buy -> insert(buy) }
} catch (e: Throwable) {}
}
#WorkerThread
private fun insert(buy: Buy) {
buyDao.insertBuy(buy)
}
}
The fragment work, data are displayed, when i click on an item from that fragment(recyclerView) it work, the activity display details data. But none of the click on that activity works, like it doesn't detect the clicks. I guess it got something to do with the coroutines because when I comment out the code viewmodelScope.launch { getAllBuys()} from the BuySharedViewModel it works, because it load data from the previous call from room database, and the clicks works.
Here's the code in the detail view:
class BuyDetailActivity : AppCompatActivity() {
private lateinit var sharedViewModel: BuySharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lateinit var buy: Buy
sharedViewModel = ViewModelProviders.of(this).get(BuySharedViewModel::class.java)
val position = intent.getIntExtra("position", 0)
sharedViewModel.allBuys.observe(this, Observer<List<Buy>> { buys ->
buy = buys[position]
val binding: com.example.drake.kunuk.databinding.ActivityBuyDetailBinding =
DataBindingUtil.setContentView(this, com.example.drake.kunuk.R.layout.activity_buy_detail)
binding.buy = buy
val agentNumber = buy.agentNumber?:"+50937438713"
bnvContactAgent.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
com.example.drake.kunuk.R.id.action_call -> {
val callNumberUri = Uri.parse("tel:$agentNumber")
val callIntent = Intent(Intent.ACTION_DIAL, callNumberUri)
startActivity(callIntent)
}
com.example.drake.kunuk.R.id.action_sms -> {
val smsNumberUri = Uri.parse("sms:$agentNumber")
val smsIntent = Intent(Intent.ACTION_SENDTO, smsNumberUri)
startActivity(smsIntent)
}
com.example.drake.kunuk.R.id.action_email -> {
val uriText = "mailto:drakecolin#gmail.com" +
"?subject=" + Uri.encode("I'm interested in $agentNumber") +
"&body=" + Uri.encode("Hello, ")
val uri = Uri.parse(uriText)
val sendIntent = Intent(Intent.ACTION_SENDTO)
sendIntent.data = uri
startActivity(Intent.createChooser(sendIntent, "Send email"))
}
}
false
}
This is the code of my fragment:
class BuyFragment : Fragment() {
companion object {
fun newInstance() = BuyFragment()
}
private lateinit var viewModel: BuySharedViewModel
private val buyList = ArrayList<Buy>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Get a new or existing ViewModel from the ViewModelProvider.
viewModel = ViewModelProviders.of(this).get(BuySharedViewModel::class.java)
// Add an observer on the LiveData returned by loadAllBuys.
// The onChanged() method fires when the observed data changes and the activity is
// in the foreground.
viewModel.allBuys.observe(this, Observer<List<Buy>> { buys ->
// Update the cached copy of the words in the adapter.
buys?.let { (rvBuy.adapter as BuyAdapter).setBuys(it) }
progressBar.visibility = View.GONE
})
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.buy_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
rvBuy.layoutManager = LinearLayoutManager(context)
rvBuy.adapter = BuyAdapter(activity!!.applicationContext,
R.layout.buy_card, buyList)
progressBar.visibility = View.VISIBLE
}
}
This is the code for the BuyDao:
#Dao
interface BuyDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertBuy(vararg buys: Buy)
#Update
fun updateBuy(vararg buys: Buy)
#Delete
fun deleteBuys(vararg buys: Buy)
#Query("SELECT * FROM buys")
fun loadAllBuys(): LiveData<List<Buy>>
#Query("DELETE FROM buys")
suspend fun deleteAll()
}
viewModelScope by default uses Dispatchers.Main and it is blocking your UI.
Try this:
viewmodelScope.launch(Dispatchers.IO) { getAllBuys()}
Edit:
The problem is your setting listner on BottomNavigation when your livedata is updated which is causing this weird issue.
Replace your BuyDetailActivity code with this:
class BuyDetailActivity : AppCompatActivity() {
private lateinit var sharedViewModel: BuySharedViewModel
private var agentNumber = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityBuyDetailBinding =
DataBindingUtil.setContentView(this, R.layout.activity_buy_detail)
binding.buy = Buy()
lateinit var buy: Buy
sharedViewModel = ViewModelProviders.of(this).get(BuySharedViewModel::class.java)
val position = intent.getIntExtra("position", 0)
sharedViewModel.allBuys.observe(this, Observer<List<Buy>> { buys ->
buy = buys[position]
binding.buy = buy
binding.executePendingBindings()
agentNumber = buy.agentNumber
// set animation duration via code, but preferable in your layout files by using the animation_duration attribute
expandableTextView.setAnimationDuration(750L)
// set interpolators for both expanding and collapsing animations
expandableTextView.setInterpolator(OvershootInterpolator())
// or set them separately.
expandableTextView.expandInterpolator = OvershootInterpolator()
expandableTextView.collapseInterpolator = OvershootInterpolator()
// toggle the ExpandableTextView
buttonToggle.setOnClickListener {
buttonToggle.setText(if (expandableTextView.isExpanded) com.example.drake.kunuk.R.string.more else com.example.drake.kunuk.R.string.less)
expandableTextView.toggle()
}
// but, you can also do the checks yourself
buttonToggle.setOnClickListener {
if (expandableTextView.isExpanded) {
expandableTextView.collapse()
buttonToggle.setText(com.example.drake.kunuk.R.string.more)
} else {
expandableTextView.expand()
buttonToggle.setText(com.example.drake.kunuk.R.string.less)
}
}
//Open photoView activity when clicked
ivHouseDetail.setOnClickListener {
applicationContext
.startActivity(
Intent(
applicationContext,
ViewPagerActivity::class.java
)
.putExtra("imageList", buy.propertyImage)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
}
})
findViewById<BottomNavigationView>(R.id.bnvContactAgent)?.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.action_call -> {
Log.e("BIRJU", "Action call")
val callNumberUri = Uri.parse("tel:$agentNumber")
val callIntent = Intent(Intent.ACTION_DIAL, callNumberUri)
startActivity(callIntent)
}
R.id.action_sms -> {
Log.e("BIRJU", "Action SMS")
val smsNumberUri = Uri.parse("sms:$agentNumber")
val smsIntent = Intent(Intent.ACTION_SENDTO, smsNumberUri)
startActivity(smsIntent)
}
R.id.action_email -> {
Log.e("BIRJU", "Action Email")
val uriText = "mailto:drakecolin#gmail.com" +
"?subject=" + Uri.encode("I'm interested in $agentNumber") +
"&body=" + Uri.encode("Hello, ")
val uri = Uri.parse(uriText)
val sendIntent = Intent(Intent.ACTION_SENDTO)
sendIntent.data = uri
startActivity(Intent.createChooser(sendIntent, "Send email"))
}
}
false
}
}
}

Passing Variable to Interface

Trying to pass the device ID to the interface and I am not getting anywhere. I essentially want to store the device ID to a variable and put it in the #Get for the interface.
I tried using #Path but I am not familiar enough with it to use it.
The url needs to look like this on the call
http://xxx.xx.xxx.xxx/apps/api/109/devices/65?access_token=xxxxxx
Interface
interface DeviceDetailsAPIClient {
#GET("devices/<item id here>")
fun getDevicesDetailsAsync(#Query("access_token") access_token: String): Deferred<Response<DeviceDetails>>
}
MainActivity2 where the ID is passed to
class MainActivity2 : AppCompatActivity() {
private val tag : String = MainActivity2::class.java.simpleName
var deviceID: String = intent.getStringExtra("deviceID")
private lateinit var adapterDetails: DeviceDetailsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
rv_devices.layoutManager = LinearLayoutManager(this)
rv_devices.hasFixedSize()
adapterDetails = DeviceDetailsAdapter(listOf()) { deviceDetails: DeviceDetails -> deviceDetails }
rv_devices.adapter = adapterDetails
loadDeviceDetails()
}
private fun loadDeviceDetails() {
GlobalScope.launch(Dispatchers.Main) {
try {
val webResponseDetails = deviceDetailsApi.getDevicesDetailsAsync(access_token = "xxxxxx").await()
if (webResponseDetails.isSuccessful) {
val deviceDetails: DeviceDetails? = webResponseDetails.body()
Log.d(tag, deviceDetails?.toString())
//adapterDetails.deviceDetails = deviceDetails ?: listOf()
adapterDetails.notifyDataSetChanged()
} else {
Log.e(tag, "Error ${webResponseDetails.code()}")
Toast.makeText(this#MainActivity2, "Error ${webResponseDetails.code()}", Toast.LENGTH_LONG).show()
}
} catch (e: IOException) {
Log.e(tag, "Exception " + e.printStackTrace())
Toast.makeText(this#MainActivity2, "Exception ${e.message}", Toast.LENGTH_LONG).show()
}
}
}
}
This is the var I am storing the ID in
var deviceID: String = intent.getStringExtra("deviceID")
Now how do I get that to the interface?
The #Path annotation can be used to change the URL:
interface DeviceDetailsAPIClient {
#GET("devices/{deviceId}")
fun getDevicesDetailsAsync(
#Path("deviceId") deviceId: Long,
#Query("access_token") access_token: String
): Deferred<Response<DeviceDetails>>
}

Categories

Resources