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>
Related
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.
if I any change in firebase like delete, update. The data in recyclerView is duplicated if any of those CRUD occur, so I added temporary swipeRefresh to refresh the activity but this solution doesn't make sense.
This image below explain when I update data in firebase and what happend in RecyclerView
MainDashBoard.kt
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.database.ValueEventListener
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
class MainDashBoard : AppCompatActivity(), OnItemPatientClickListener{
data class PatientDataItem(val patientName: String, val patientMessage: String)
private lateinit var auth: FirebaseAuth
lateinit var swipeRefreshLayout: SwipeRefreshLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_dash_board)
var database = FirebaseDatabase.getInstance().reference
var patientDataItems = ArrayList<PatientDataItem>()
val patientRecycler = findViewById<RecyclerView>(R.id.patient_recycler)
val patienDashboardprogressBar = findViewById<ProgressBar>(R.id.patientDashboardprogressBar)
val noDataMain = findViewById<TextView>(R.id.no_data_main_dashboard)
swipeRefreshLayout = findViewById(R.id.swipe)
patientRecycler.layoutManager = LinearLayoutManager(this)
patientRecycler.adapter = MainDashboardAdapter(patientDataItems, this)
auth = FirebaseAuth.getInstance()
val user = auth.currentUser
val patientsListener = object : ValueEventListener {
override fun onDataChange(p0: DataSnapshot) {
val patients = p0.child("users").child(user!!.uid)
if (p0.value == null ){
noDataMain.visibility = View.VISIBLE
}else{
noDataMain.visibility = View.GONE
for (i in p0.children){
var patientName = i.key.toString()
var patientMessage = i.value.toString()
patientDataItems.add(PatientDataItem(patientName, patientMessage))
}
}
patientRecycler.scrollToPosition(patientDataItems.size-1)
patienDashboardprogressBar.visibility = View.GONE
}
override fun onCancelled(error: DatabaseError) {
println("error")
}
}
database.child("location").child("users").child(user!!.uid).addValueEventListener(patientsListener)
// database.child("location").addValueEventListener(postListener)
swipeRefreshLayout.setOnRefreshListener {
startActivity(intent);
Handler(Looper.getMainLooper()).postDelayed(Runnable {
swipeRefreshLayout.isRefreshing = false
}, 4000)
}
}
override fun onItemClick(patientDataItems: PatientDataItem) {
val patientMacAddressName = patientDataItems.patientName
val dashboardIntent = Intent(this, DashboardActivity::class.java)
dashboardIntent.putExtra("macAddressNamePatient", patientMacAddressName)
startActivity(dashboardIntent)
}
}
MainDashBoardAdapter.kt
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.example.ard_here.R
class MainDashboardAdapter(private val patientDataSet: ArrayList<MainDashBoard.PatientDataItem>,
private val onPatientClickListener: OnItemPatientClickListener): RecyclerView.Adapter<MainDashboardAdapter.PatientCustomHolder>(){
override fun getItemCount(): Int {
return patientDataSet.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PatientCustomHolder {
var layoutInflater = LayoutInflater.from(parent?.context)
var cellForRow = layoutInflater.inflate(R.layout.main_patient_layout, parent, false)
return PatientCustomHolder(cellForRow)
}
override fun onBindViewHolder(holder: PatientCustomHolder, position: Int) {
holder.bindItems(patientDataSet[position])
holder.patientLayout.setOnClickListener {
onPatientClickListener.onItemClick(patientDataSet[position])
}
}
class PatientCustomHolder(v: View): RecyclerView.ViewHolder(v){
val patientLayout: ConstraintLayout = v.findViewById(R.id.patient_layout)
val patientName: TextView = v.findViewById(R.id.patient_name)
val patientMessage : TextView = v.findViewById(R.id.patient_message)
fun bindItems(data_item: MainDashBoard.PatientDataItem){
patientName.text = data_item.patientName
patientMessage.text = data_item.patientMessage
}
}
}
OnItemPatientClickListener.kt
interface OnItemPatientClickListener {
fun onItemClick(patientDataItems: MainDashBoard.PatientDataItem)
}
clear your data container then bind it again in recyclerview.
or you have mvvm pattern, you can use live data to observe data source and if there is any changes, your activity will easily notified and make some ui changes
Since you're reading the data with addValueEventListener, which means that:
The data from the path is read from the database right away, and passed to your onDataChange.
The client the continues monitor the path, and if anything changes it calls your onDataChange again with all data at the path.
In your onDataChange you're only ever adding data to patientDataItems. That works well the first time the data is loaded, so #1 above. But if you add or change a single child node (#2 above), you get called with all data at the path again. So that's when you end up duplicating the items in the view.
The simplest solution is to clear patientDataItems whenever onDataChange get called:
override fun onDataChange(p0: DataSnapshot) {
patientDataItems.clear()
...
in my Android project i need to use one of my viewmodel's response in another fragment. But whenever i try to get that value it is always null. I tried to get it from its own fragment with livedata and it works!. But it is not same for another fragment. Here my viewmodel code that has the response ;
package com.tolgahantutar.bexworkfloww.ui.auth
import android.content.Intent
import android.view.View
import android.widget.Toast
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.tolgahantutar.bexworkfloww.data.network.repositories.AuthorizeSessionRepository
import com.tolgahantutar.bexworkfloww.data.network.repositories.GetDomainRepository
import com.tolgahantutar.bexworkfloww.data.network.repositories.GetUserRepository
import com.tolgahantutar.bexworkfloww.data.network.responses.GetUserResponse
import com.tolgahantutar.bexworkfloww.ui.home.HomeActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class AuthViewModel #ViewModelInject constructor (
private val authorizeSessionRepository: AuthorizeSessionRepository,
private val getDomainRepository: GetDomainRepository,
private val getUserRepository: GetUserRepository
):ViewModel() {
var userName :String?=null
var password: String ? = null
val isLoading = MutableLiveData<Boolean>()
private val location = "bexfatest.saasteknoloji.com"
val isSuccessfull = MutableLiveData<Boolean>()
var getUserResponseMutable = MutableLiveData<GetUserResponse>()
fun onClickUserLogin(view: View){
val sessionID = 0
val authorityID = 0
val loginType = "System"
viewModelScope.launch {
if(!(userName==null||password==null)){
isLoading.value = true
val authResponse = userLogin(sessionID,authorityID,userName!!,password!!,loginType)
if(authResponse.Result){
isLoading.value=false
val domainResponse=getDomain(location)
**`val getUserResponse`** = getUser(authResponse.authorizeSessionModel!!.ID,"Bearer "+domainResponse.getDomainModel.ApiKey)
if (getUserResponse.result){
isSuccessfull.value=true
getUserResponseMutable.value=getUserResponse
}
//Toast.makeText(view.context, "Login Successfull", Toast.LENGTH_LONG).show()
val intent = Intent(view.context,HomeActivity::class.java)
view.context.startActivity(intent)
}else{
isLoading.value=false
Toast.makeText(view.context, "Login Failed!!", Toast.LENGTH_LONG).show()
}
}
else{
Toast.makeText(view.context, "Kullanıcı adı ve şifre boş bırakılamaz!!", Toast.LENGTH_SHORT).show()
}
}
}
suspend fun userLogin(
SessionID : Int,
AuthorityID: Int,
UserName: String,
Password : String,
LoginType: String
)= withContext(Dispatchers.IO){authorizeSessionRepository.userLogin(SessionID, AuthorityID, UserName, Password, LoginType)}
suspend fun getUser(
id: Int,
authorization : String
)= withContext(Dispatchers.Main){getUserRepository.getUser(id,authorization)}
suspend fun getDomain(
Location: String
)= withContext(Dispatchers.IO){getDomainRepository.getDomain(Location)}
}
i need to get getUserResponse variable in my AddressBook Fragment like this ;
package com.tolgahantutar.bexworkfloww.ui.addressbook
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import com.tolgahantutar.bexworkfloww.R
import com.tolgahantutar.bexworkfloww.ui.auth.AuthViewModel
import dagger.hilt.android.AndroidEntryPoint
#AndroidEntryPoint
class AdressBookFragment : Fragment() {
private val addressBookViewModel : AdressBookViewModel by viewModels()
private val authViewModel : AuthViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.adress_book_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
//addressBookViewModel.getContact(2,authViewModel.userResponseDelegate!!.getUserValue.apiKey)
authViewModel.getUserResponseMutable.observe(viewLifecycleOwner, Observer {
if (it.result){
Toast.makeText(requireContext(), "asdadasd", Toast.LENGTH_SHORT).show()
}
})
addressBookViewModel.isSuccessfull.observe(viewLifecycleOwner, Observer {
if (it){
Toast.makeText(requireContext(), "ContactList Get Successfully", Toast.LENGTH_SHORT).show()
}
})
}
}
but observe is always null how can i get that getUserResponse inside my AddressBookFragment ??
The AuthViewModel is most likely destroyed when you leave your AuthFragment (which I assume you have), so your AdressBookFragment is getting a new instance of the ViewModel, which doesn't retain any data from the previous screen.
I'd recommend that you store the result from AuthViewModel into a Repository or some other global state object and retrieve it from there.
ViewModels hold temporary data needed for a screen, but whether the user is authenticated matters to the whole application, not just a single screen. As such it should be stored someplace that lives for as long as the whole app, and is reachable from anywhere, like a Repository.
private val authViewModel : AuthViewModel by viewModels()
is equivalent to
private val viewModel by lazy {
ViewModelProvider(this).get(AuthViewModel::class.java)
}
So as you can see this is passed as viewModelStoreOwner.
Since i guess that you do net use AdressBookFragment as viewModelStoreOwner in your other fragment you are creating a new viewmodel in this fragment.
You probably need a shared viewmodel which you get when you use
private val viewModel by lazy {
ViewModelProvider(requireActivity()).get(AuthViewModel::class.java)
}
in both fragments
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.