I'm currently writing a currency converter app. Everything is working fine however I'm having trouble formatting the API response. I'm getting "CurrencyResponse(conversion_result=1)" when what I really want is to get the value of conversion_result alone.
Below is the relevant code.
Thank you.
CurrencyApi.kt
package com.example.currencyconverter.data
import com.example.currencyconverter.BuildConfig
import retrofit2.http.GET
import retrofit2.http.Path
interface CurrencyApi {
companion object {
const val BASE_URL = "https://v6.exchangerate-api.com/v6/"
}
#GET("{access_key}/pair/{currency_from}/{currency_to}/{amount}")
suspend fun convert(
#Path("access_key") access_key: String = BuildConfig.API_KEY,
#Path("currency_from") currency_from: String,
#Path("currency_to") currency_to: String,
#Path("amount") amount: Double? = 0.0
): CurrencyResponse
}
CurrencyResponse.kt
package com.example.currencyconverter.data
import com.squareup.moshi.Json
data class CurrencyResponse(
#Json(name="conversion_result") var conversion_result: String
){
}
CurrencyRepository.kt
package com.example.currencyconverter.data
import com.example.currencyconverter.BuildConfig
import javax.inject.Inject
import javax.inject.Singleton
#Singleton
class CurrencyRepository #Inject constructor(private val currencyApi: CurrencyApi) {
suspend fun getConversionRate(baseCurrency: String, toCurrency: String, amount: Double?): CurrencyResponse {
return currencyApi.convert(BuildConfig.API_KEY, baseCurrency, toCurrency, amount)
}
}
CurrencyViewModel.kt
package com.example.currencyconverter.ui
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.currencyconverter.data.CurrencyRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
#HiltViewModel
class CurrencyViewModel #Inject constructor(private val repository: CurrencyRepository): ViewModel() {
private val _conversionResult = MutableLiveData<String>()
val conversionResult: LiveData<String> = _conversionResult
fun getConversionRate(baseCurrency: String, toCurrency: String, amount: Double?) {
viewModelScope.launch {
_conversionResult.value = repository.getConversionRate(baseCurrency, toCurrency, amount).toString()
}
}
}
HomeFragment.kt
package com.example.currencyconverter.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.example.currencyconverter.R
import com.example.currencyconverter.databinding.FragmentHomeBinding
import dagger.hilt.android.AndroidEntryPoint
#AndroidEntryPoint
class HomeFragment : Fragment(R.layout.fragment_home) {
private val viewModel by viewModels<CurrencyViewModel>()
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentHomeBinding.bind(view)
lateinit var firstCurrency: String
lateinit var secondCurrency: String
binding.apply {
spinnerFirst.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
adapterView: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
firstCurrency = adapterView?.getItemAtPosition(position).toString()
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
spinnerSecond.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
adapterView: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
secondCurrency = adapterView?.getItemAtPosition(position).toString()
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
}
binding.button.setOnClickListener {
val stringInTextField = binding.amountText.text.toString()
val amount = stringInTextField.toDoubleOrNull()
if(amount==null) {
binding.resultText.text = " "
}
viewModel.getConversionRate(firstCurrency, secondCurrency, amount)
viewModel.conversionResult.observe(viewLifecycleOwner) {
binding.resultText.text = it
}
}
}
}
The problem lies in
fun getConversionRate(baseCurrency: String, toCurrency: String, amount: Double?) {
viewModelScope.launch {
_conversionResult.value = repository.getConversionRate(baseCurrency, toCurrency, amount).toString()
}
}
to call toString() on a data class formats it the way you see it.
Since you only want the conversion_result you need to change it to
fun getConversionRate(baseCurrency: String, toCurrency: String, amount: Double?) {
viewModelScope.launch {
_conversionResult.value = repository.getConversionRate(baseCurrency, toCurrency, amount).conversion_result
}
}
Related
I am retrieving data from a json weblink using retrofit2 which is working as intended and I checked the debugger and it shows that the elements are being added in the private variables, however, I am attempting to display the String elements that I obtained from the json link and added to an ArrayList and when I call the RecyclerViewAdapter to display the results I keep getting an IndexOutOfBoundsException saying that the elements are in fact null?
In the FetchContent object init block I am calling getRetroFitData() to connect to the json URL and retrieve the data in the json link and then save the results in the private variables in that same function. After that when I call the recycler view I add the results to the data class by using the getter functions to create each Item in the jsonITEMS variable but during this process I get the exception thrown.
I cannot figure out why or how the ArrayList becomes null after I iterate them:
ItemFragment.kt
package com.example.fetch_coding_exercise
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
/**
* A fragment representing a list of Items.
*/
const val PRIMARY_URL = "" // Intentionally left empty do to contents of link
class ItemFragment : Fragment() {
private var columnCount = 1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
columnCount = it.getInt(ARG_COLUMN_COUNT)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_item_list, container, false)
// Set the adapter
if (view is RecyclerView) {
with(view) {
layoutManager = when {
columnCount <= 1 -> LinearLayoutManager(context)
else -> GridLayoutManager(context, columnCount)
}
adapter = MyItemRecyclerViewAdapter(FetchContent.jsonITEMS)
}
}
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
companion object {
// TODO: Customize parameter argument names
const val ARG_COLUMN_COUNT = "column-count"
// TODO: Customize parameter initialization
#JvmStatic
fun newInstance(columnCount: Int) =
ItemFragment().apply {
arguments = Bundle().apply {
putInt(ARG_COLUMN_COUNT, columnCount)
}
}
}
}
MyItemRecyclerViewAdapter.kt
package com.example.fetch_coding_exercise
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import com.example.fetch_coding_exercise.placeholder.PlaceholderContent.PlaceholderItem
import com.example.fetch_coding_exercise.databinding.FragmentItemBinding
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
/**
* [RecyclerView.Adapter] that can display a [PlaceholderItem].
* TODO: Replace the implementation with code for your data type.
*/
class MyItemRecyclerViewAdapter(private val values: List<FetchContent.FetchDataItem>)
: RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
FragmentItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = values[position]
holder.userIdView.text = item.id
holder.listIdView.text = item.listId
holder.nameView.text = item.name
}
override fun getItemCount(): Int = values.size
inner class ViewHolder(binding: FragmentItemBinding) : RecyclerView.ViewHolder(binding.root) {
val userIdView: TextView = binding.userID
val listIdView: TextView = binding.listID
val nameView: TextView = binding.name
override fun toString(): String {
return super.toString() + " '" + nameView.text + "'"
}
}
}
FetchContent.kt
package com.example.fetch_coding_exercise
import java.util.ArrayList
import java.util.HashMap
object FetchContent {
val jsonITEMS: MutableList<FetchDataItem> = ArrayList()
private val COUNT = 25
init {
fetchedData().getRetroFitData()
for (i in 0..COUNT) {
addItem(createFetchItem(i))
}
}
private fun addItem(item: FetchDataItem) {
jsonITEMS.add(item)
}
private fun createFetchItem(position: Int): FetchDataItem {
return FetchDataItem(
fetchedData().getID(position), fetchedData().getListID(position), fetchedData().getName(position))
}
data class FetchDataItem(val id: String, val listId: String, val name: String) {
override fun toString(): String = listId
}
}
fetchedData.kt
package com.example.fetch_coding_exercise
import android.util.Log
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.jar.Attributes
class fetchedData : ArrayList<fetchedDataItem>() {
private val retroId = arrayListOf<String>()
private val retroListId = arrayListOf<String>()
private val retroName = arrayListOf<String>()
private var retroCOUNT = 0
fun getRetroFitData() {
val retrofitBuilder = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(PRIMARY_URL)
.build()
.create(interfaceAPI::class.java)
val retrofitData = retrofitBuilder.getData()
retrofitData.enqueue(object : Callback<List<FetchContent.FetchDataItem>?> {
override fun onResponse(
call: Call<List<FetchContent.FetchDataItem>?>,
response: Response<List<FetchContent.FetchDataItem>?>
) {
val responseBody = response.body()!!
retroCOUNT = responseBody.size
for (i in responseBody.indices) {
if (!responseBody[i].name.isNullOrEmpty()) {
retroId.add(responseBody[i].id)
retroListId.add(responseBody[i].listId)
retroName.add(responseBody[i].name)
} else {
retroCOUNT--
}
}
println("retroId: " + retroId)
println("retroListId: " + retroListId)
println("retroName: " + retroName)
}
override fun onFailure(call: Call<List<FetchContent.FetchDataItem>?>, t: Throwable) {
Log.d("FetchContent", "onFailure:"+t.message)
}
})
}
fun getID(index: Int): String {
return if (retroId[index].isEmpty())
"null"
else
retroId[index]
}
fun getListID(index: Int): String {
return retroListId[index]
}
fun getName(index: Int): String {
return retroName[index]
}
}
Logcat Error Message
2021-09-30 14:26:54.022 28416-28416/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.fetch_coding_exercise, PID: 28416
java.lang.ExceptionInInitializerError
at com.example.fetch_coding_exercise.ItemFragment.onCreateView(ItemFragment.kt:48)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2963)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:518)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:112)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1647)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3128)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:3072)
at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:251)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:502)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:246)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1435)
at android.app.Activity.performStart(Activity.java:8018)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3475)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Caused by: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.get(ArrayList.java:437)
at com.example.fetch_coding_exercise.fetchedData.getID(fetchedData.kt:57)
at com.example.fetch_coding_exercise.FetchContent.createFetchItem(FetchContent.kt:48)
at com.example.fetch_coding_exercise.FetchContent.<clinit>(FetchContent.kt:33)
I'm trying to pass data from one activity to another through an intent with putExtra. The thing is, I get the error that says: None of the following functions can be called with the arguments supplied.
I'm not sure why it's not working, since the variable that I'm referencing is in the viewholder. Any clue as to what's going on and how to fix it would help a lot.
This is my recycler adapter:
package com.example.newsapp
import android.content.Intent
import android.icu.text.CaseMap
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat.startActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.newsapp.databinding.NewsItemBinding
import com.squareup.picasso.Picasso
class RecyclerAdapter (
private var Titles: List<String>,
private var Images: List<String>,
private var Authors: List<String>,
private var Descriptions: List<String>
) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>(){
inner class ViewHolder(
view: View
): RecyclerView.ViewHolder(view){
private val binding = NewsItemBinding.bind(view)
val itemTitle: TextView = binding.tvTitle
val itemImage: ImageView = binding.ivNewsImage
fun bind(urlToImage:String){
Picasso.get().load(urlToImage).into(binding.ivNewsImage)
}
init {
itemImage.setOnClickListener{
val intent = Intent(view.context, PostActivity::class.java)
intent.putExtra("title",itemTitle)
view.context.startActivity(intent)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.news_item, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemTitle.text = Titles[position]
val itemImage = Images[position]
holder.bind(itemImage)
}
override fun getItemCount(): Int {
return Titles.size
}
}
The part with the issue is this one:
init {
itemImage.setOnClickListener{
val intent = Intent(view.context, PostActivity::class.java)
intent.putExtra("title",itemTitle) view.context.startActivity(intent)
}
}
This is my main activity:
package com.example.newsapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.newsapp.databinding.ActivityMainBinding
import kotlinx.coroutines.*
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.lang.Exception
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var adapter: RecyclerAdapter
private val newsTitles = mutableListOf<String>()
private val newsImages = mutableListOf<String>()
private val newsAuthors = mutableListOf<String>()
private val newsDescriptions = mutableListOf<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
makeAPIRequest()
}
private fun initRecyclerView() {
adapter = RecyclerAdapter( newsTitles, newsImages)
binding.rvNews.layoutManager = LinearLayoutManager(this)
binding.rvNews.adapter = adapter
}
private fun addToList(title:String, image:String, author:String, description:String){
newsTitles.add(title)
newsImages.add(image)
newsAuthors.add(author)
newsDescriptions.add(description)
}
private fun makeAPIRequest() {
val api = Retrofit.Builder()
.baseUrl("https://newsapi.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(APIService::class.java)
GlobalScope.launch(Dispatchers.IO){
val response = api.getNews()
val posts = response.body()
try{
if (posts != null) {
for(art in posts.Articles){
Log.i("Main Activity", "Result = $art")
addToList(art.Title,art.urlToImage, art.Author, art.Description)
}
}
withContext(Dispatchers.Main){
initRecyclerView()
}
} catch (e:Exception){
Log.e("Main Activity", e.toString())
}
}
}
}
intent.putExtra("title",itemTitle)
Here, itemTitle is a TextView. You cannot pass a TextView between activities. You could switch to:
intent.putExtra("title",itemTitle.text)
...to put the text from the TextView into the extra.
Also, you might want to consider whether these should be separate activities or just two fragments (or composables) in a single activity.
For some reason, onClick isn't being registered with my adapter. I'm using the MVVM pattern and I've made sure that all the pieces are tied together but for the life of me I can't figure out why this won't work.
StoreFragment
package com.example.brandroidtest.main
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.fragment.findNavController
import com.example.brandroidtest.databinding.FragmentStoreBinding
class StoreFragment : Fragment() {
//Will Create a ViewModelProivders object of class DetailViewModel the first time viewModel is used
//Allows us to move this code from on create to the declaration
private val viewModel: StoreViewModel by lazy {
val factory = StoreViewModelFactory(requireNotNull(activity).application)
ViewModelProviders.of(this, factory).get(StoreViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.i("onCreateView", "StoreFragment created")
val binding = FragmentStoreBinding.inflate(inflater)
binding.setLifecycleOwner(this)
binding.viewModel = viewModel
binding.storeList.adapter = StoreAdapter(StoreAdapter.OnClickListener {
viewModel.displayStoreDetails(it)
Log.i("inside OnClickListener", "after displayDetails")
})
Log.i("between adapter.onclick", "and viewModel observe")
viewModel.selectedStore.observe(this, Observer {
Log.i("observe", "inside the selectedStore observe method")
if (null != it) {
this.findNavController().navigate(
StoreFragmentDirections.actionMainListFragmentToDetailFragment(
it
)
)
viewModel.displayStoreDetailsComplete()
}
})
return binding.root
}
}
StoreViewModel
package com.example.brandroidtest.main
import android.app.Application
import android.content.Context
import android.net.ConnectivityManager
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.brandroidtest.model.Store
import com.example.brandroidtest.network.StoreAPI
import kotlinx.coroutines.*
enum class StoreAPIStatus {LOADING, DONE, NO_CONNECTION}
class StoreViewModel(application: Application) : AndroidViewModel(application) {
// Response from server: Either Store Data or Failure Message
private val _status = MutableLiveData<StoreAPIStatus>()
// for status of get request
//displayed when there is no internet connection or if the connection is unstable and the data is being loaded
val status: LiveData<StoreAPIStatus>
get() = _status
//internal variable accessed within this file
private val listOfStores = MutableLiveData<List<Store>>()
//external variable for anywhere else
val stores: LiveData<List<Store>>
get() = listOfStores
private val _selectedStore = MutableLiveData<Store>()
val selectedStore: LiveData<Store>
get() = _selectedStore
private var viewModelJob = Job()
private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main)
/**
* Call getStoreData() in init so we can display the result immediately.
*/
init {
Log.i("viewModel init", "inside StoreViewModel init block")
if (isNetworkConnected(application.applicationContext))
getStoreData()
else
// Log.i("Bypassed network call", "")
listOfStores.value = emptyList()
_status.value = StoreAPIStatus.NO_CONNECTION
}
/**
* Sets the value of the status LiveData to the Store API data.
*/
private fun getStoreData() {
Log.i("getStoreData()", " inside getStoreData")
coroutineScope.launch {
try {
Log.i("getStoreData()", "Inside the coroutine before getData")
_status.value = StoreAPIStatus.LOADING
var storeData = async { StoreAPI.retrofitService.getData().stores }.await()
Log.i("getStoreData()", "Inside the coroutine after getData")
_status.value = StoreAPIStatus.DONE
listOfStores.value = storeData
} catch (e: Exception) {
_status.value = StoreAPIStatus.NO_CONNECTION
listOfStores.value = ArrayList()
e.printStackTrace()
}
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
private fun isNetworkConnected(context: Context): Boolean {
val cm =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
return cm!!.activeNetworkInfo != null && cm.activeNetworkInfo.isConnected
}
//will be called to set the store as the one that was clicked
fun displayStoreDetails(store : Store){
Log.i("displayStoreDetails", "inside this method")
_selectedStore.value = store
}
//sets the selected store's value to null so that live data can be updated when we select a new store and not show us the detail apge of the same store
fun displayStoreDetailsComplete() {
Log.i("displayStoreDetailsComplete", "inside this method")
_selectedStore.value = null
}
}
StoreAdapter
package com.example.brandroidtest.main
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.brandroidtest.model.Store
import com.example.brandroidtest.databinding.ListItemBinding
class StoreAdapter(val onClickListener: OnClickListener) :
ListAdapter<Store, StoreAdapter.StoreViewHolder>(DiffCallback) {
class StoreViewHolder(private var binding: ListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(store: Store) {
binding.store = store
Log.i("Adapter bind", store.storeLogoURL)
binding.executePendingBindings()
}
}
companion object DiffCallback : DiffUtil.ItemCallback<Store>() {
override fun areItemsTheSame(oldItem: Store, newItem: Store): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Store, newItem: Store): Boolean {
return oldItem.storeID == newItem.storeID
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): StoreViewHolder {
return StoreViewHolder(ListItemBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun onBindViewHolder(holder: StoreViewHolder, position: Int) {
val store = getItem(position)
Log.i("inside onBindViewHolder", "")
holder.itemView.setOnClickListener {
Log.i("inside onBindViewHolder", "setOnClickListener")
onClickListener.onClick(store)
}
holder.bind(store)
}
class OnClickListener(val clickListener: (store: Store) -> Unit) {
fun onClick(store: Store) {
Log.i("inside onClick", "click is being registered ${store.city}")
return clickListener(store)
}
}
}
StoreDetailFragment
package com.example.brandroidtest.detailed
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.example.brandroidtest.R
import com.example.brandroidtest.databinding.FragmentStoreDetailBinding
/**
* A simple [Fragment] subclass.
*/
class StoreDetailFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val application = requireNotNull(activity).application
val binding = FragmentStoreDetailBinding.inflate(inflater)
binding.setLifecycleOwner(this)
val store = StoreDetailFragmentArgs.fromBundle(arguments!!).selectedStore
val viewModelFactory = StoreDetailViewModelFactory(store, application)
binding.viewModel = ViewModelProviders.of(this, viewModelFactory).get(StoreDetailViewModel::class.java)
return binding.root
}
}
StoreDetailViewModel
package com.example.brandroidtest.detailed
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.brandroidtest.model.Store
class StoreDetailViewModel(store: Store, application: Application) : AndroidViewModel(application) {
private val _selectedStore = MutableLiveData<Store>()
val selectedStore : LiveData<Store>
get() = _selectedStore
init {
_selectedStore.value = store
}
}
I have no idea why onClick won't work and the Detail Fragment won't show because of it
Here is the project link: https://drive.google.com/open?id=1m8R8HvCt4m0KIp_IwdeO1YdB5yY8A8LK
The issue come from your adapter item layout.
The height of every item show be wrap_content. But you are using a ScrollView as the root view of your item view.
Remove the useless ScrollView and also the next LinearLayout. You layout should become like this:
<LinearLayout
...
android:padding="16dp"/>
<ImageView
android:id="#+id/store_logo"
.../>
<LinearLayout
android:id="#+id/store_detail"
...>
</LinearLayout>
i have one fragment which run asynctask to get data by parsing api and storing in array list.
but what i want to do is that i want to print that list from another class so that i can create search filter.
package com.example.rsquare_android_fixed.mywork.building
import android.os.AsyncTask
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.size
import androidx.fragment.app.Fragment
import com.example.rsquare_android_fixed.R
import com.example.test.mainF.Address_data
import kotlinx.android.synthetic.main.fragment_building.*
import org.json.JSONArray
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL
class buildingFragment: Fragment(){
private val list=ArrayList<Address_data>()
private val list2=ArrayList<Address_map>()
public var list3=ArrayList<Address_all>()
public var check_list3:Int = 0
override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {
val inflate=inflater.inflate(R.layout.fragment_building, container, false)
val url="https://manage-dev.rsquare.co.kr/api/sample/buildings"
AsyncTaskHandleJson().execute(url)
return inflate
}
inner class AsyncTaskHandleJson: AsyncTask<String, String, String>(){
override fun doInBackground(vararg url: String?): String {
var text:String
val connection= URL(url[0]).openConnection() as HttpURLConnection
try{
connection.connect()
text=connection.inputStream.use{it.reader().use{reader->reader.readText()}}
}
finally{
connection.disconnect()
}
return text
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
handleJson(result)
naverGeo()
}
//json parsing
private fun handleJson(jsonString: String?) {
val jsonArray= JSONArray(jsonString)
//val list=ArrayList<Address_data>()
var x=0
while(x<jsonArray.length()){
val jsonObject=jsonArray.getJSONObject(x)
list.add(
Address_data(
jsonObject.getString("address"),
jsonObject.getString("bunji"),
jsonObject.getString("streetAddress"),
jsonObject.getString("updateAt")
)
)
x++
}
val adapter=MW_Adapter_list(requireActivity(),list)
data_list.adapter=adapter
// val a = list[0]
// a.address
list.forEach { println(it) }
println("listsize1="+list.size)
}
}
fun naverGeo(){
println("naverGeo")
var query:String
println("listsize2="+list.size)
for(i in 0..list.size-1){
var a = list[i]
query=a.address
println("test : $i")
println("query : $query")
var url2 = "https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode?X-NCP-APIGW-API-KEY-ID=key&X-NCP-APIGW-API-KEY=pw&query="+query
AsyncTaskNaver().execute(url2)
}
}
inner class AsyncTaskNaver:AsyncTask<String,String,String>(){
override fun doInBackground(vararg url2: String?): String {
var text:String
val connection=URL(url2[0]).openConnection() as HttpURLConnection
try{
connection.connect()
text=connection.inputStream.use{it.reader().use{reader->reader.readText()}}
}
finally{
connection.disconnect()
}
return text
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
handleJson2(result)
//merge to longitude and latitude with original data
if(list2.size==30){
mergeList()
}
}
private fun handleJson2(jsonString: String?){
val jsonObj=JSONObject(jsonString)
val jsonArray=JSONArray(jsonObj.get("addresses").toString())
var x=0
while(x<jsonArray.length()){
val jsonObject=jsonArray.getJSONObject(x)
list2.add(
Address_map(
jsonObject.getString("roadAddress"),
jsonObject.getString("x"), //long
jsonObject.getString("y") //lat
)
)
x++
}
//println(list2.size)
}
}
fun mergeList(){
//println(list2.size)
for (i in 0..list.size-1){
var list_api=list[i]
var list_naver=list2[i]
var query_api=list_api.address
var query_naver=list_naver.raddress
var bunji_api=list_api.bunji
var street_api=list_api.streeetAddress
var long_naver=list_naver.x
var lat_naver=list_naver.y
//by comparing first 2 letters, last 3 letters from address to merge them into one
if(query_api.takeLast(3)==query_naver.takeLast(3)&&query_api.take(2)==query_naver.take(2)){
list3.add(
Address_all(
query_api,
bunji_api,
street_api,
long_naver,
lat_naver
)
)
}
}
check_list3=1
list3.forEach { println(it) }
}
}
and below is my another code where i want to print list
package com.example.rsquare_android_fixed.mywork.search
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import com.example.rsquare_android_fixed.R
import com.example.rsquare_android_fixed.mywork.building.MWFragment
import com.example.rsquare_android_fixed.mywork.building.MW_Adapter_list
import com.example.rsquare_android_fixed.mywork.building.buildingFragment
import com.naver.maps.geometry.LatLng
import com.naver.maps.geometry.LatLngBounds
import com.naver.maps.map.NaverMap
import com.naver.maps.map.NaverMapSdk
import com.naver.maps.map.overlay.Marker
import kotlinx.android.synthetic.main.fragment_building.*
import kotlinx.android.synthetic.main.fragment_search.*
import kotlinx.android.synthetic.main.list_search.*
class searchFragment: Fragment(){
private var list: ListView?=null
private var adapter:SearchAdapter?=null
private var editsearch:SearchView?=null
private var addrList:Array<String>?=null
//lateinit var naverMap:NaverMap
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view:View = inflater.inflate(R.layout.fragment_search, container, false)
//list=findViewById(R.id.search_list) as ListView
val a = buildingFragment()
if(a.check_list3==0){
println("nulnunlnlunlnl")
}
else{
println(a.list3)
println("not null")
//println(a.list3)
}
return view
}
/*override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//NaverMapSdk.getInstance(requireContext()).client=NaverMapSdk.NaverCloudPlatformClient("dp3mci4foz")
val a = buildingFragment()
}
*/
}
i think my 2nd code is calling list before asynctask from 1st code is done. and this leads me to print null list.
so is there any method that 2nd code to notice that 1st code has finished asynctask?
(sry my code is way to dirty)
Create an interface
interface CallBack {
fun asyncompleted(list:ArrayList<Address_all>)
}
Implement in searchFragment.
class searchFragment: Fragment(), CallBack {
fun asyncCompleted(list:ArrayList<Address_all>) {
println(list)
}
}
Create a method in buildingFragment
var callback: CallBack
setCallback(value) { callback = value }
set callback after creating buildingFragment instance in searchFragment
val a = buildingFragment()
a.setCallback(this)
call asyncompleted method after setting list3
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
handleJson2(result)
//merge to longitude and latitude with original data
if(list2.size==30){
mergeList()
}
callback.asyncompleted(list3)
}
I was following some guide and tweaking variable name to fit my own project. My problem was most people dont appears to load from Room db into an adapter so when trying to do upon opening the apps my view appears empty. while i checked the DB using DB browser for SQLite i can see my db contain 7 items.
Main activity
package com.example.android.budgetproject
import android.arch.persistence.room.RoomDatabase
import android.content.Context
import android.content.SharedPreferences
import android.databinding.DataBindingUtil
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import com.example.android.budgetproject.databinding.ActivityMainBinding
import com.example.android.budgetproject.popUp.DepenseDetails
import com.example.android.budgetproject.popUp.NewBudget
class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener, BudgetVM.NewButtonClick {
lateinit var budget : SharedPreferences
var budgetTotal: String? = null
val vm = BudgetVM()
lateinit var db: RoomDatabase
lateinit var adapter: TransactionAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.vm = vm
//Get the Total Budget in the shared pref and display it
budget = getSharedPreferences("Budget", Context.MODE_PRIVATE)
budget.registerOnSharedPreferenceChangeListener(this)
budgetTotal = budget.getString("BudgetTotal", null)
adapter = TransactionAdapter()
//Open Dialog if new user.
if(budgetTotal == null) {
NewBudget.createPopUp(this)
} else {
vm.budgetTotal.set(budgetTotal)
}
vm.listener = this
//Room
db = MyDatabase.getInstance()
//Adapter
val handler = Handler()
Thread({
val transactionFromDb = MyDatabase.mInstance?.transactionDao()?.getAllTransaction()
handler.post({
if (transactionFromDb != null) adapter.addTransaction(transactionFromDb)
})
}).start()
adapter.notifyDataSetChanged()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
vm.budgetTotal.set(budgetTotal)
}
override fun newTransactionClicked(){
DepenseDetails.createTransaction(this)
}
}
The Dao:
package com.example.android.budgetproject
import android.arch.persistence.room.*
import com.example.android.budgetproject.Transaction
import android.arch.persistence.room.Delete
import android.arch.persistence.room.Dao
#Dao
interface TransactionDao {
#Query("SELECT * FROM `transaction`")
fun getAllTransaction(): List<Transaction>
#Query("SELECT * FROM `transaction` where uid IN (:transactionId)")
fun findTransactionById(transactionId: IntArray): List<Transaction>
#Insert(onConflict = 1)
fun insertTransaction(transaction: Transaction)
#Delete
fun deleteTransaction(transaction: Transaction)
}
The Adapter:
package com.example.android.budgetproject
import android.support.v7.recyclerview.extensions.ListAdapter
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import kotlinx.android.synthetic.main.activity_main.view.*
import kotlinx.android.synthetic.main.row_depense_fragment.view.*
/**
* Created by olivier on 2018-03-13.
*/
class TransactionAdapter(private val clickListener: ButtonClick? = null):
ListAdapter<Transaction, TransactionAdapter.ViewHolder>(TransactionDiffCallback()) {
interface ButtonClick{
fun clicked(id: Int)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return ViewHolder(inflater.inflate(R.layout.row_depense_fragment, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
fun bind(transaction: Transaction){
itemView.tv_depense_description.text = transaction.description
itemView.tv_depense.text = transaction.depense
itemView.tv_depense_date.text = transaction.date
itemView.setOnClickListener{
clickListener?.clicked(adapterPosition)
}
}
}
private var transactionList = ArrayList<Transaction>()
fun addTransaction(list: List<Transaction>){
transactionList.clear()
transactionList.addAll(list)
notifyDataSetChanged()
}
}
And the util i use to create DB item.
package com.example.android.budgetproject
import android.os.AsyncTask
import android.widget.Toast
class TransactionCreation {
fun transactionCreation(depense: String, description: String, date: String){
val newTransaction = Transaction(depense, description, date)
val db = MyDatabase
object : AsyncTask<Void, Void, Void>() {
override fun doInBackground(vararg params: Void?): Void? {
db.getInstance().transactionDao().insertTransaction(newTransaction)
return null
}
override fun onPostExecute(result: Void?) {
super.onPostExecute(result)
Toast.makeText(BudgetApplication.getContext(), "Transaction saved", Toast.LENGTH_SHORT)
}
}.execute()
}
}
Here's also a link to the project github should be up to date.
https://github.com/OlivierLabelle/BudgetProject
Thanks for checking.
Set notifyDataSetChanged within the thread in your MainActivity via the handler.