I'm trying to show the recycler view's data on my app. The thing is, even though the NetworkStatus is successful (I can tell because I don't get the toast's message and the loader disappears and I can also see the data in the logcat), the info is not displayed. I am not sure if the error is in the way I'm calling the recycler view on my MainActivity or in the RecyclerAdapter but any idea as to where the problem is would be very helpful.
This is the RecyclerAdapter:
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.app.mortyapp.databinding.ItemDetailBinding
class RecyclerAdapter(private var characterList: List<Character>): RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerAdapter.ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemDetailBinding.inflate(
layoutInflater,
parent,
false
)
return ViewHolder(binding)
}
override fun getItemCount(): Int = characterList.size
override fun onBindViewHolder(holder: RecyclerAdapter.ViewHolder, position: Int) {
holder.bind(characterList[position])
}
fun setCharacterList(characterList: List<Character>){
this.characterList = characterList
notifyDataSetChanged()
}
inner class ViewHolder(
private val binding: ItemDetailBinding
) : RecyclerView.ViewHolder(binding.root){
fun bind(character: Character) {
with(binding){
val itemName: TextView = binding.tvName
val itemGender: TextView = binding.tvGender
itemName.text = character.name
itemGender.text = character.gender
}
}
}
}
This is the MainActivity:
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.app.mortyapp.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val characters = mutableListOf<Character>()
private lateinit var progressBar: ProgressBar
private lateinit var recyclerAdapter: RecyclerAdapter
private val viewModel: MainViewModel by viewModels(
factoryProducer = {MainViewModelFactory()}
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
progressBar = binding.ProgressBar
progressBar.visibility = View.INVISIBLE
setObservers()
initRecyclerView()
}
private fun initRecyclerView() {
with(binding.rvCharacters){
layoutManager = LinearLayoutManager(context)
recyclerAdapter = RecyclerAdapter(characters).apply {
setCharacterList(characters)
}
}
}
private fun setObservers(){
viewModel.characterList.observe(this, Observer {
when(it.status){
NetworkStatus.LOADING ->{
//show loading state
progressBar.visibility = View.VISIBLE
}
NetworkStatus.SUCCESS -> {
//hide loading state
progressBar.visibility = View.INVISIBLE
//render character list
recyclerAdapter.setCharacterList(characters)
}
NetworkStatus.ERROR -> {
//show error message
Toast.makeText(this,"Error loading content", Toast.LENGTH_SHORT).show()
//hide loading state
progressBar.visibility = View.INVISIBLE
}
}
})
}
}
API response:
import com.google.gson.annotations.SerializedName
data class Character (
#SerializedName("id") val id: Int,
#SerializedName("name") val name: String,
#SerializedName("gender") val gender: String
)
data class CharacterListResponse(
#SerializedName("results") val results: List<Character>
)
Remote data source:
package com.app.mortyapp
import com.app.mortyapp.Model.CharacterService
import com.app.mortyapp.Model.RetrofitServices
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class CharacterRemoteDataSource {
fun getCharacterList(networkResponse: NetworkResponse<List<Character>>) {
val service = RetrofitServices.instance
.create(CharacterService::class.java)
.getCharacterList()
service.enqueue(object : Callback<CharacterListResponse> {
override fun onResponse(
call: Call<CharacterListResponse>,
response: Response<CharacterListResponse>
) {
val resource = response.body()?.run {
if (results.isNotEmpty())
Resource(NetworkStatus.SUCCESS, results)
else
Resource(NetworkStatus.ERROR)
} ?: run {
Resource(NetworkStatus.ERROR)
}
networkResponse.onResponse(resource)
}
override fun onFailure(call: Call<CharacterListResponse>, t: Throwable) {
networkResponse.onResponse(Resource(NetworkStatus.ERROR, message = t.message))
}
})
}
}
interface NetworkResponse<T> {
fun onResponse(value: Resource<T>)
}
Set adapter for Recyclerview in
setupRecylerview ()
adapter = recyclerAdapter
NetworkStatus.SUCCESS -> {
//hide loading state
progressBar.visibility = View.INVISIBLE
//render character list
recyclerAdapter.setCharacterList(characters)
recyclerAdapter.notifydatasetchanged()//add this line
}
I think most problems found with recyclerView isn't linked to it, but with some adjourning codes. For example, a very similar problem was solved by finding out that the adapter POJO class was retrieving 0 rather than the actual size of the array list.
See the solved problem here:
Android Recycler View not loading Data (Peculiar problem, Not a Duplicate)
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.
I have implemented Result.Success and Result.Error in my ViewModel class but I am getting following error in my SecondActivity.kt C:\Users\Edgar\AndroidStudioProjects\GiphyAndroidApp\app\src\main\java\com\example\giphyandroidapp\ui\SecondActivity.kt: (52, 52): Type mismatch: inferred type is Result<List?> but List was expected
below my SecondActivity.kt
package com.example.giphyandroidapp.ui
import com.example.giphyandroidapp.utils.Result
import com.example.giphyandroidapp.utils.Error
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
import com.example.giphyandroidapp.utils.Constants
import com.example.giphyandroidapp.viewmodel.GiphyTaskViewModel
import com.example.giphyandroidapp.adapters.GiphyTaskAdapter
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import com.example.giphyandroidapp.databinding.ActivitySecondBinding
import com.example.giphyandroidapp.utils.Success
import dagger.hilt.android.AndroidEntryPoint
#AndroidEntryPoint
class SecondActivity : AppCompatActivity() ,View.OnClickListener {
lateinit var binding: ActivitySecondBinding
val viewModel: GiphyTaskViewModel by viewModels()
var text: String = ""
lateinit var myadapter: GiphyTaskAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySecondBinding.inflate(layoutInflater)
setContentView(binding.root)
val intent = intent
if (intent.hasExtra("text")) {
// list= intent.getParcelableArrayListExtra<Parcelable>("response")!! as List<DataItem>
text = intent.getStringExtra("text").toString()
if (text != null) {
bindData()
}
}
binding.getresultbtn.setOnClickListener(this)
}
private fun bindData() {
viewModel.getGifsFromText(Constants.Api_Key, text, Constants.Limit)
viewModel.giphyresponse.observe(this,
{ response ->
when (response) {
is Success -> {
myadapter = GiphyTaskAdapter(this, response.data)
}
is Error -> {
} // Handle error case
}
binding.imgsrecycler.apply {
adapter = myadapter
layoutManager = GridLayoutManager(this#SecondActivity, 2)
visibility = View.VISIBLE
}
binding.progressBar.visibility = View.GONE
})
}
override fun onClick(v: View?) {
var mytext: String = ""
mytext = binding.txtword.text.toString()
if (mytext.equals("")) {
Toast.makeText(this, "you must enter word", Toast.LENGTH_LONG).show()
} else {
if (text.equals(mytext)) {
val builder = android.app.AlertDialog.Builder(this)
builder.setTitle("Winner")
builder.setMessage("Excellent , you win \n Play game again ?!")
builder.setPositiveButton("Yes") { dialog, which ->
startActivity(Intent(this, MainActivity::class.java))
}
builder.setNegativeButton("No") { dialog, which ->
dialog.dismiss()
}
builder.create().show()
} else {
val builder = android.app.AlertDialog.Builder(this)
builder.setTitle("Loser")
builder.setMessage("fail , you lose \n Game over ! \n Play game again ?!")
builder.setPositiveButton("Yes") { dialog, which ->
startActivity(Intent(this, MainActivity::class.java))
}
builder.setNegativeButton("No") { dialog, which ->
dialog.dismiss()
}
builder.create().show()
}
}
}
}
below GiphyAdapter.kt
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil.load
import com.example.giphyandroidapp.databinding.ListItemBinding
import com.example.giphyandroidapp.model.gifsresponse.DataItem
import com.example.giphyandroidapp.model.gifsresponse.Images
import com.example.giphyandroidapp.utils.Result
class GiphyTaskAdapter(val context: Context,val list:List<DataItem>) : RecyclerView.Adapter<GiphyTaskAdapter.GiphyTaskViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GiphyTaskAdapter.GiphyTaskViewHolder {
return GiphyTaskViewHolder(ListItemBinding.inflate(LayoutInflater.from(parent.context),parent,false))
}
override fun onBindViewHolder(holder: GiphyTaskAdapter.GiphyTaskViewHolder, position: Int) {
var item:DataItem = list.get(position)
var imageitem: Images =item.images
holder.binding.apply {
img.load(imageitem.original.url){
crossfade(true)
crossfade(1000)
}
}
holder.itemView.setOnClickListener {mview->
}
}
override fun getItemCount()=list.size
inner class GiphyTaskViewHolder(val binding: ListItemBinding):
RecyclerView.ViewHolder(binding.root)
}
below my ViewModel class where I have implemented Result.Success and Result.Error logic
#HiltViewModel
class GiphyTaskViewModel
#Inject
constructor(private val giphyTaskRepository: GiphyTaskRepository):ViewModel()
{
var giphyresponse = MutableLiveData<Result<List<DataItem>?>>()
fun getGifsFromText(apikey:String,text:String,limit:Int)= viewModelScope.launch {
giphyTaskRepository.getGifsFromText(apikey,text,limit).let { response->
if(response?.isSuccessful){
var list=response.body()?.data
giphyresponse.postValue(Success(list))
}else{
Error(Exception(response.message()))
}
}
}
}
below Repository class
class GiphyTaskRepository
#Inject
constructor(private val giphyTaskApiService: GiphyTaskApiService)
{
suspend fun getGifsFromText(apikey:String,text:String,limit:Int)=
giphyTaskApiService.getGifsFromText(apikey,text,limit)
}
below my network interface
interface GiphyTaskApiService {
#GET("gifs/search")
suspend fun getGifsFromText(
#Query("api_key") api_key:String,
#Query("q") q:String ,
#Query("limit") limit:Int
):Response<GiphyResponse>
}
below GiphyResponse.kt
#Parcelize
data class GiphyResponse(
#field:SerializedName("pagination")
val pagination: Pagination,
#field:SerializedName("data")
val data: List<DataItem>,
#field:SerializedName("meta")
val meta: Meta
) : Parcelable
below Result class
sealed class Result<T>
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Any?>()
I want to know what I have to do in order to successfully implement result.success and result.error class and pass correct paremeter so that avoid mismatch error
I am getting following mismatch after implementing murat's answer
GiphyTaskAdapter expects List, but in ViewModel you have Result<List?>.
class GiphyTaskAdapter(val context: Context,val list:List<DataItem>)
var giphyresponse = MutableLiveData<Result<List<DataItem>?>>()
So, for example, you can change a type in ViewModel.
var giphyresponse: MutableLiveData<List<DataItem>> = MutableLiveData()
...
val list: List<DataItem> = response.body()?.data ?: emptyList()
giphyresponse.postValue(list)
UPD:
sealed class can be handled as Marat wrote. Also, you should handle null type.
when(response) {
is Success -> {
val list: List<DataItem> = response.data ?: emptyList()
myadapter = GiphyTaskAdapter(this, list)
.......
}
is Error -> // Handle error case
}
Updated
You need to check your response if it is successful or not. Something like:
when(response) {
is Success -> {
myadapter = GiphyTaskAdapter(this, response.data)
.......
}
is Error -> // Handle error case
}
I am trying to make a todo list app and want to save all todo title in the shared preference (I know it's a bad idea) but only one value is stored in the shared preference and only that loads on app restart. How to add and load multiple items from shared preference in recyclerView ?
MainActivity.kt
import android.content.Context
import android.content.SharedPreferences
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private lateinit var todoAdapter: TodoAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
todoAdapter = TodoAdapter(mutableListOf())
rvTodoItems.adapter = todoAdapter
rvTodoItems.layoutManager = LinearLayoutManager(this)
val sharedPreferences = getSharedPreferences("myPref", Context.MODE_APPEND)
val editor = sharedPreferences.edit()
fun getData():List<String>{
val name = sharedPreferences.getString("todoName",null)
val list = mutableListOf<String>()
list.add(name.toString())
return list
}
fun loadData(list:List<String>){
for(item in list){
val td = Todo(item)
todoAdapter.addTodo(td)
}
}
val data:List<String> = getData()
loadData(data)
btnAddTodo.setOnClickListener {
val todoTitle = addTodo.text.toString()
if(todoTitle.isNotEmpty()){
val todo = Todo(todoTitle)
todoAdapter.addTodo(todo)
editor.apply {
putString("todoName",todoTitle)
apply()
}
}
}
btnRemoveTodo.setOnClickListener {
todoAdapter.deleteDoneTodos()
}
}
}
Todo data class file (Todo.kt)
data class Todo(
val title:String,
var isChecked: Boolean = false
)
TodoAdapter.kt
import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.text.style.StrikethroughSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_todo.view.*
class TodoAdapter(
private val todos: MutableList<Todo>
) : RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {
class TodoViewHolder(itemView:View): RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
return TodoViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.item_todo,
parent,
false
)
)
}
fun addTodo(todo: Todo){
todos.add(todo)
notifyItemInserted(todos.size-1)
}
fun deleteDoneTodos(){
todos.removeAll { todo ->
todo.isChecked
}
notifyDataSetChanged()
}
private fun toggleStrikeThrough(tvTodoTitle:TextView, isChecked: Boolean){
if(isChecked){
tvTodoTitle.paintFlags = tvTodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG
}else{
tvTodoTitle.paintFlags = tvTodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
}
}
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
val currentTodo = todos[position]
holder.itemView.apply {
tvTodoTitle.text = currentTodo.title
cbDone.isChecked = currentTodo.isChecked
toggleStrikeThrough(tvTodoTitle,currentTodo.isChecked)
cbDone.setOnCheckedChangeListener{_, isChecked ->
toggleStrikeThrough(tvTodoTitle,isChecked)
currentTodo.isChecked = !currentTodo.isChecked
}
}
}
override fun getItemCount(): Int {
return todos.size
}
}
Please take a look at code and help me out. Thanks :)
If you specifically want to save in shared preferences you can convert your list to json and save then read as json and convert to list. You can use Google's Gson library for that.
//Save
editor.putString("todoName", Gson().toJson(list))
//Read
val list = Gson().fromJson(sharedPreferences.getString("todoName"), object: TypeToken<List<String>>(){}.javaClass)
But I kindly suggest you to use Room library for this kind of stuff.
official doc: https://developer.android.com/training/data-storage/room
You can use work like with Parcelable classes.
#Parcelize
data class ToDoKit {
var todos: ArrayList<Todo>
}: Parcelable
and use SharedPreferences.Editor.putParcelable and SharedPreferences.Editor.getParcelable to operate with data.
I'm a beginner so please bare with me.
I have a viewholder that has an onclicklistener.
The aim of the click is to send a Url string into another fragment using Jetpack Navigation (hopefully i did it right)
the Url is being created within the dataclass itself.
but i keep getting this error:
kotlin.UninitializedPropertyAccessException: lateinit property galleryItem has not been initialized
I tried working around using companion object and other ways, nothing worked... is there a solution for this?
here is the view holder and data class
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import androidx.core.os.bundleOf
import androidx.navigation.Navigation
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.saheralsous.android.R
import com.saheralsous.android.database.remote.model.PagingData
class RecyclerViewPhotoAdapter() :
PagingDataAdapter<PagingData.GalleryItem, PhotoHolder>(
diffCallback = DiffCallback
) {
override fun onBindViewHolder(holder: PhotoHolder, position: Int) {
getItem(position)?.let {
holder.bind(it)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.photo_item_view,parent,false)
return PhotoHolder(view)
}
}
class PhotoHolder(view: View):
RecyclerView.ViewHolder(view), View.OnClickListener {
private val imageButtom: ImageButton = view.findViewById(R.id.ImageButton)
private lateinit var galleryItem: PagingData.GalleryItem //page. 594
#SuppressLint("ResourceType")
fun bind(galleryItem : PagingData.GalleryItem){
/*
idTextView.text = galleryItem.id
ownerTextView.text = galleryItem.owner
titleTextView.text = galleryItem.title
urlTextView.text = galleryItem.url
*/
galleryItem.url.let { url ->
Glide.with(itemView)
.load(url)
.override(350,350)
.into(imageButtom)
}
}
init {
imageButtom.setOnClickListener(this)
}
override fun onClick(v: View?) {
println("item was clicked")
val bundle = bundleOf("url" to galleryItem.photoPageUri ) <-- here is the issue
Navigation.findNavController(v!!).navigate(
R.id.action_photoGalleryFragment_to_photoPageFragment,
bundle)
}
}
object DiffCallback : DiffUtil.ItemCallback<PagingData.GalleryItem>() {
override fun areItemsTheSame(oldItem: PagingData.GalleryItem, newItem: PagingData.GalleryItem): Boolean {
// Id is unique.
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: PagingData.GalleryItem, newItem: PagingData.GalleryItem): Boolean {
return oldItem == newItem
}
}
data class
import android.net.Uri
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
data class PagingData(
val total : Int =0,
val Page: Int = 0,
val photos : List<GalleryItem>
){
val endOfPage = total == Page
#Keep
data class GalleryItem(
#SerializedName("id")
val id: String,
#SerializedName("owner")
val owner: String,
#SerializedName("title")
var title: String,
#SerializedName("url_s")
val url: String) {
val photoPageUri : Uri <-- here is the value
get() {
return Uri.parse("https://www.flickr.com/photos/")
.buildUpon()
.appendPath(owner)
.appendPath(id)
.build()
}
}
}
As the error states, you haven't initialised the galleryItem instance variable in your PhotoHolder. Add this inside your bind method:
this.galleryItem = galleryItem
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.