I want to make reset button.
when I click reset button, ischecked will be set to false for R.id.item1, R.id.item2, and R.id.item3.
when I click button, I want to Group 'Mmenu' be checked to false
I tried button.setOnClickListener(){item.isChecked = false} in onCreateOptionsMenu(menu: Menu)
but didn't work...
And I tried same thing behind when (item.itemId) {R.id.itemRotate ->
but didn't work too...
class MainActivity : AppCompatActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
title = "제주도 풍경"
button.setOnClickListener(){
imageView1.visibility = View.INVISIBLE
imageView1.rotation = Float.parseFloat("0")
edtAngle.setText("0")
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val mInflater = menuInflater
mInflater.inflate(R.menu.menu1, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem) : Boolean {
when (item.itemId) {
R.id.itemRotate -> {
imageView1.visibility = View.VISIBLE
imageView1.rotation = Float.parseFloat(edtAngle.text.toString())
return true
}
R.id.item1 -> {
imageView1.visibility = View.VISIBLE
imageView1.setImageResource(R.drawable.jeju2)
item.isChecked = true
return true
}
R.id.item2 -> {
imageView1.visibility = View.VISIBLE
imageView1.setImageResource(R.drawable.jeju14)
item.isChecked = true
return true
}
R.id.item3 -> {
imageView1.visibility = View.VISIBLE
imageView1.setImageResource(R.drawable.jeju6)
item.isChecked = true
return true
}
}
return false
}
}
You can do this using a HashMap :
var checked: HashMap<Int, Boolean> = HashMap()
Initialize to false
val ids = listOf(R.id.item1, R.id.item2, R.id.item3)
ids.forEachIndexed { index, _ ->
checked[index] = false
}
Then
when (item.itemId) {
R.id.itemRotate -> {
imageView1.visibility = View.VISIBLE
imageView1.rotation = Float.parseFloat(edtAngle.text.toString())
return true
}
R.id.item1 -> {
imageView1.visibility = View.VISIBLE
imageView1.setImageResource(R.drawable.jeju2)
checked[item.itemId] = true
return true
}
R.id.item2 -> {
imageView1.visibility = View.VISIBLE
imageView1.setImageResource(R.drawable.jeju14)
checked[item.itemId] = true
return true
}
R.id.item3 -> {
imageView1.visibility = View.VISIBLE
imageView1.setImageResource(R.drawable.jeju6)
checked[item.itemId] = true
return true
}
}
To reset:
fun reset() {
ids.forEachIndexed { index, _ ->
checked[index] = false
}
}
To be called here:
button.setOnClickListener(){
imageView1.visibility = View.INVISIBLE
imageView1.rotation = Float.parseFloat("0")
edtAngle.setText("0")
reset()
}
invalidateOptionsMenu()
this is solution to reset!!
Related
I need to reduce my app size by collect recurring functions in one place
All functions works fine Except RewardedAd
I want to return a value confirming that ad was shown until end by set variable named adRewardedBoolean to True
Here's my Kotlin code
//Rewarded
private const val adRewardedID = "ca-app-pub-3306064401573277/5337641258"
private var adRewardedBoolean: Boolean = false
private var adRewardedIsLoading = false
private var adRewarded: RewardedAd? = null
private var adRewardAmount: Int = 0
private var adRewardedTimes: Int = 0
//Call loadRewardedAd from any activities
//Ads.loadRewardedAd(this)
fun loadRewardedAd(context: Context): Boolean {
context as Activity
if (adRewarded == null) {
adRewardedIsLoading = true
val adRequest = AdRequest.Builder().build()
RewardedAd.load(context,
adRewardedID,
adRequest,
object : RewardedAdLoadCallback() {
override fun onAdFailedToLoad(adError: LoadAdError) {
adRewardedIsLoading = false
adRewarded = null
if (adRewardedTimes == 3) {
adRewardAmount++
adRewardedBoolean = true
log("onAdFailedToLoad")
//checkRewardedAd(adRewardAmount)
} else {
adRewardedTimes++
loadRewardedAd(context)
}
}
override fun onAdLoaded(rewardedAd: RewardedAd) {
adRewarded = rewardedAd
adRewardedIsLoading = false
adRewarded?.fullScreenContentCallback =
object : FullScreenContentCallback() {
override fun onAdDismissedFullScreenContent() {
adRewarded = null
adRewardedBoolean = true
log("onAdDismissedFullScreenContent")
}
override fun onAdFailedToShowFullScreenContent(adError: AdError) {
adRewarded = null
loadRewardedAd(context)
}
override fun onAdShowedFullScreenContent() {
}
}
adRewarded?.show(context) { rewardItem ->
adRewardAmount = rewardItem.amount
}
}
})
}
log(adRewardedBoolean.toString())
return adRewardedBoolean
}
I got blocked for this problem, Api will call and get the objects from the response and it will display on the recyclerview. But it is not showing,
fetchProductCategories will do the api call.
prepareProducts will handle the fetched from fetchProductCategories
Fragment:
#AndroidEntryPoint
class ProductsWelcomeFragment : BaseProductsFragment<ProductsWelcomeViewModel, ProductsWelcomeFragmentBinding>() {
private val TAG = "ProductsWelcomeFragment"
#Inject
lateinit var animationQueue: AnimationQueue
override fun getViewModelClass(): KClass<ProductsWelcomeViewModel> = ProductsWelcomeViewModel::class
override fun getContentViewRes(): Int = R.layout.products_welcome_fragment
private val cordovaViewModel: CordovaViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/**
* Initialize only once when everytime accessing products screen.
* */
sharedViewModel.initialSetUpRequest()
viewModel.fetchProductCategories()
}
override fun onBindView() {
with(dataBinding) {
viewModel = this#ProductsWelcomeFragment.viewModel
title = getString(R.string.products_screen_welcome_header)
recyclerViewProducts.configure()
executePendingBindings()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.productCategoriesLiveData.observe(viewLifecycleOwner) {
Log.i(TAG, it.toString())
viewModel.items.observe(viewLifecycleOwner) {
viewModel.prepareProducts()
}
}
viewModel.showProgress.observe(viewLifecycleOwner) {}
viewModel.failedAtRetrievingData.observe(viewLifecycleOwner) {
// showErrorScreen(true)
}
}
private fun RecyclerView.configure() {
with(dataBinding.recyclerViewProducts) {
addItemBindings(SectionHeaderItemViewBinder)
addItemBindings(SpaceItemViewDtoBinder)
addItemBindings(getListButtonItemViewBinder(ProductItem.NormalProduct::dto, ::onProductItemClick))
addItemBindings(getListBigtileItemViewBinder(ProductItem.FeaturedProduct::dto, ::onProductItemClick))
}
}
private fun onProductItemClick(product: ProductItem.NormalProduct) {
when (product.id) {
else -> { // TODO : )}
}
// TODO : implement move to another screen
}
private fun onProductItemClick(product: ProductItem.FeaturedProduct) {
// TODO : implement move to another screen
}
private fun showErrorScreen(isDataRetrievalError: Boolean) {
val dismissAction = ErrorHandlingAction(
getString(R.string.native_done),
null,
null,
ButtonType.PRIMARY
) { dismissDialog ->
dismissDialog.dismiss()
if (isDataRetrievalError) {
findNavController().popBackStack()
}
}
DialogFactory().showBottomDialog(
fragmentManager = parentFragmentManager,
window = requireActivity().window,
title = getString(R.string.native_error_title),
description = getString(R.string.online_identity_something_went_wrong_error_description),
iconId = R.drawable.ic_error_thick_exclamation_icon,
errorHandlingActions = arrayOf(dismissAction),
isHtmlDescription = true
)
}
private fun openCordovaScreen(destination: CordovaPage) {
cordovaViewModel.requestPage(destination)
findNavController().popBackStack(R.id.homeScreenMainFragment, false)
}
}
ViewModel:
#HiltViewModel
class ProductsWelcomeViewModel #Inject constructor(
private val productsRepository: ProductsRepository
) : BaseRequestViewModel(), ViewModelWithItems {
private val TAG = "ProductsWelcomeViewModel"
private val _failedAtRetrievingData = SingleLiveEvent<Boolean>()
val failedAtRetrievingData: LiveData<Boolean> = _failedAtRetrievingData
private val _showProgress = MutableLiveData<Boolean>()
val showProgress: LiveData<Boolean> = _showProgress
private val onProductCategories: SingleLiveEvent<ProductBasketsCategoriesModel?> = SingleLiveEvent()
val productCategoriesLiveData: LiveData<ProductBasketsCategoriesModel?> = onProductCategories
private val _items: MutableLiveData<List<Any>> = MutableLiveData()
override val items: LiveData<List<Any>> = _items
fun fetchProductCategories() {
viewModelScope.launch {
request({ productsRepository.getProductCategories() },
success = { response -> onProductCategories.value = response },
failure = { })
}
}
fun prepareProducts() {
_items.value = mutableListOf<Any>().apply {
productCategoriesLiveData.value?.embedded?.categories?.filter { categories ->
categories.isFeatured == true }?.let { filteredCategories ->
add(HeaderItem(ListSectionHeaderItemViewDto(
text = TextLine(
textRes = if (filteredCategories.isNotEmpty()) {
R.string.products_screen_welcome_header } else { null }
)
)
))
filteredCategories.toItems()?.let { addAll(it) }
}
productCategoriesLiveData.value?.embedded?.categories?.filter { categories ->
categories.isFeatured == false }.let { filteredCategories ->
if (filteredCategories != null) {
add(HeaderItem(ListSectionHeaderItemViewDto(
text = TextLine(
textRes = if (filteredCategories.isNotEmpty()) {
R.string.products_screen_welcome_header } else { null }
)
)
))
}
filteredCategories.toItems()?.let { addAll(it) }
}
}
}
private fun List<CategoriesItemModel>?.toItems(): List<ProductItem>? =
this?.mapIndexed { index, item ->
if (item.isFeatured == true) {
ProductItem.FeaturedProduct(
id = item.id as Any,
name = item.name,
dto = BigTileDto(
title = TextLine(text = item.name),
image = item.id.toString().let { toFeatureIcon(it, item.isFeatured) },
description = TextLine(item.description.toString()),
background = BackgroundType.SINGLE
)
)
} else {
ProductItem.NormalProduct(
id = item.id as Any,
name = item.name,
dto = ListButtonItemViewDto(
firstLine = TextLine(text = item.name),
rightDrawable = R.drawable.ic_arrow_right,
separatorDrawable = R.drawable.list_divider_margin_start_72dp,
background = index.indexToBackgroundType(this.size),
avatarDto = AvatarDto(iconBackgroundColor = R.color.ubs_concrete, avatarSize = AvatarDto.AvatarSize.SMALL_ICON_SIZE, iconId = toFeatureIcon(
item.id, item.isFeatured
)),
)
)
}
}
private fun toFeatureIcon(id: String?, isFeature: Boolean?): Int = if (ProductFeature.verify(id) == true) {
ProductFeature.icon()
} else { if (isFeature == true) { R.drawable.abc_vector_test } else {
R.drawable.balloon_illustration } }
}
I am new to development and I want to learn how to use the MVVM pattern. I have a problem with the fact that I need to implement the methods that I have in SignUpFragment in the ViewModel class. Please help me understand how to finally do this. I will be very grateful! Thanks in advance
please do not send links to materials, I want to understand my example, thanks
My fragment
class SignUpFragment : Fragment() {
private val viewModel: SignUpViewModel by activityViewModels()
private val binding: FragmentSignUpBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val viewModel:SignUpViewModel = ViewModelProviders.of(this)[SignUpViewModel::class.java]
val binding: FragmentSignUpBinding =
DataBindingUtil.inflate(
inflater, R.layout.fragment_sign_up, container, false
)
return binding.root
}
// call next sign up screen window
fun callNextSignUpScreen(view: View) {
//validate form
if (!(validateFullName() || validateUserName() as Boolean || validateEmail() as Boolean || validatePassword() as Boolean)) {
return
} else {
binding?.signupNextBtn?.setOnClickListener {
findNavController()
.navigate(
R.id.action_signUpFragment_to_signUpSecondFragment
)
}
}
//add transition
val pairs: Array<Pair<*, *>?> = arrayOfNulls(4)
pairs[0] = Pair<View, String>(binding?.signupBackButton, "transition_back_arrow_btn")
pairs[1] = Pair<View, String>(binding?.signupLoginBtn, "transition_login_btn")
pairs[2] = Pair<View, String>(binding?.signupNextBtn, "transition_next_btn")
pairs[3] = Pair<View, String>(binding?.signupTitleText, "transition_title_btn")
}
//validate field full name
private fun validateFullName(): Boolean {
val validName: String = binding!!.signupFullname.editText?.text.toString().trim()
return if (validName.isEmpty()) {
binding.signupFullname.error = "field can not be Empty"
false
} else {
binding.signupFullname.error = null
binding.signupFullname.isErrorEnabled = false
true
}
}
//validate field user name
private fun validateUserName(): Any {
val userName: String = binding!!.signupUserName.editText?.text.toString().trim()
val checksPaces: Regex = "[a-zA-Z0-9._-]+#[a-z]+\\.++[a-z]+".toRegex()
return if (userName.isEmpty()) {
binding.signupUserName.error = "field can not be Empty"
false
} else (if (userName.length > 20) {
binding.signupUserName.error = "Username is too large!"
false
} else if (!userName.matches(checksPaces)) {
binding.signupEmail.error = "Invalid Email"
} else {
binding.signupUserName.error = null
binding.signupUserName.isErrorEnabled = false
true
})
}
//validate field email
private fun validateEmail(): Any {
val email: String = binding!!.signupEmail.editText?.text.toString().trim()
val checkEmail: Regex = "[a-zA-Z0-9._-]+#[a-z]+\\.++[a-z]+".toRegex()
return if (email.isEmpty()) {
binding.signupEmail.error = "field can not be Empty"
false
} else if (!email.matches(checkEmail)) {
binding.signupEmail.error = "Invalid Email"
} else {
binding.signupEmail.error = null
binding.signupEmail.isErrorEnabled = false
true
}
}
//validate field password
private fun validatePassword(): Any {
val password: String = binding!!.signupPassword.editText?.text.toString().trim()
val checkPassword: Regex =
"^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[##\$%^&+=])(?=\\S+\$).{8,}\$".toRegex()
return if (password.isEmpty()) {
binding.signupPassword.error = "Password should contain4 characters!"
false
} else if (!password.matches(checkPassword)) {
binding.signupPassword.error = "Invalid Email"
} else {
binding.signupPassword.error = null
binding.signupPassword.isErrorEnabled = false
true
}
}
I am trying to create a browser having a navigation drawer and a recyclerView holding Tabs for new website. Navigation drawer pops out but recyclerview not showing.
here's my adapter code:
class NavigationTabAdapter (context: Context, contentData:ArrayList<Tab>) :
RecyclerView.Adapter<NavigationTabAdapter.NavigationViewHolder>() {
var mcontext:Context?=null
var contentList:ArrayList<Tab>?=null
var tabInterface:TabInterface?=null
init {
mcontext=context
contentList=contentData
tabInterface=context as TabInterface
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NavigationViewHolder {
val itemView=LayoutInflater.from(parent.context).inflate(R.layout.row_custom_recycler_tab, parent, true)
return NavigationViewHolder(itemView)
}
override fun onBindViewHolder(holder: NavigationViewHolder, position: Int) {
holder.row_text?.setText(contentList?.get(position)?.url)
holder.row_img_remove?.setOnClickListener {
if (contentList!!.size>1)
{
tabInterface?.deleteItem(contentList?.get(position)?.id!!,position)
}
}
here's my mainActivity xml:
enter code //Navigation drawer
var drawerLayout:DrawerLayout?=null
var tabsDatabase:TabsDatabase?=null
var navigationRecyclerView:RecyclerView?=null
var navigationTabAdapter:NavigationTabAdapter?=null
var listWebLinks:ArrayList<Tab>?=null
var itemClickedPosition:Int=-1
var idForClickedPosition:Int=-1
var url:String?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolbar=findViewById<Toolbar>(R.id.appBar)
setSupportActionBar(toolbar)
drawerLayout=findViewById(R.id.drawer_layout)
val toggle=ActionBarDrawerToggle(this#MainActivity,drawerLayout,toolbar,
R.string.navigation_drawer_open,R.string.navigation_drawer_close)
drawerLayout?.addDrawerListener(toggle)
toggle.syncState()
tabsDatabase= TabsDatabase(this)
navigationRecyclerView=findViewById(R.id.nav_recycler_view)
navigationRecyclerView?.layoutManager=LinearLayoutManager(this)
navigationRecyclerView?.itemAnimator=DefaultItemAnimator()
listWebLinks= ArrayList()
searchView = findViewById(R.id.search_View)
webview = findViewById(R.id.webImage)
progressBar = findViewById(R.id.progressBar)
inputMethodManager=getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
mainScreen=findViewById(R.id.mainScreen)
webview!!.settings.javaScriptEnabled = true
webview!!.webViewClient = object :WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?,
url:String
): Boolean {
view!!.loadUrl(url)
return true
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
if (url.equals("about:blank"))
{
mainScreen?.visibility=View.VISIBLE
webview?.visibility
return
}
if (!(progressBar!!.isShown))
{
progressBar?.visibility = View.VISIBLE
view!!.visibility=View.GONE
}
searchView!!.onActionViewExpanded()
searchView!!.setQuery(webview!!.url,false)
searchView!!.clearFocus()
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
if (progressBar!!.isShown)
{
progressBar!!.visibility = View.GONE
view!!.visibility=View.VISIBLE
}
findViewById<ImageView>(R.id.backwardImage)!!.isEnabled=true
findViewById<ImageView>(R.id.backwardImage)!!.setImageResource(R.drawable.undo_blue)
if (!(webview!!.canGoForward()))
{
findViewById<ImageView>(R.id.forwardImage)?.isEnabled=false
findViewById<ImageView>(R.id.forwardImage)?.setImageResource(R.drawable.redo_blue)
}
findViewById<ImageView>(R.id.homeImage)?.isEnabled=true
findViewById<ImageView>(R.id.homeImage)?.setImageResource(R.drawable.home_orange)
if (itemClickedPosition!=-1)
{
updateItem(idForClickedPosition,url)
}
}
}
searchView?.setOnQueryTextListener(object :SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String): Boolean {
try {
val bool:Boolean=URLUtil.isValidUrl(query)
if (bool)
{
webview!!.loadUrl(query)
}
else
{
webview!!.loadUrl("https://"+query.replace("",""))
findViewById<LinearLayout>(R.id.mainScreen).visibility = View.GONE
}
findViewById<ImageView>(R.id.backwardImage).isEnabled=true
findViewById<ImageView>(R.id.backwardImage).setImageResource(R.drawable.undo_orange)
findViewById<ImageView>(R.id.homeImage)?.isEnabled=true
findViewById<ImageView>(R.id.homeImage).setImageResource(R.drawable.home_orange)
inputMethodManager!!.hideSoftInputFromWindow(currentFocus?.windowToken,0)
}
catch (e:Exception)
{
Toast.makeText(this#MainActivity,""+e.printStackTrace(),Toast.LENGTH_SHORT).show()
}
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
return false
}
})
try {
listWebLinks = tabsDatabase?.readData()
if (listWebLinks!!.size == 0)
{
tabsDatabase?.insertData("Home Page")
listWebLinks = tabsDatabase?.readData()
itemClickedPosition = 0
} else {
itemClickedPosition = listWebLinks!!.size - 1
}
navigationTabAdapter = NavigationTabAdapter(this, listWebLinks!!)
navigationRecyclerView?.adapter = navigationTabAdapter
navigationTabAdapter?.notifyDataSetChanged()
navigationRecyclerView?.setHasFixedSize(true)
} catch (e: Exception) {
e.printStackTrace()
}
database file
class TabsDatabase:SQLiteOpenHelper {
constructor(context: Context) : super(context, DB_NAME, null, DB_VERSION)
override fun onCreate(sqLiteDatabase: SQLiteDatabase?) {
sqLiteDatabase?.execSQL("CREATE TABLE" + TABLE_NAME + "(" + COL_ID +
" INTEGER PRIMARY KEY," + COL_URL + "TEXT);")
}
override fun onUpgrade(sqLiteDatabase: SQLiteDatabase?, p1: Int, p2: Int) {
sqLiteDatabase?.execSQL("Drop table IF EXISTS" + TABLE_NAME)
}
fun insertData(url:String):Boolean?{
var result = true
try{
val db= this.writableDatabase
var cv=ContentValues()
cv.put(COL_URL,url)
db.insert(TABLE_NAME,null,cv)>0
db.close()
}catch (e:Exception) {
result = false
}
return result
}
fun updateData(id:Int,url:String):Boolean?{
var result = true
try{
val db= this.writableDatabase
var cv=ContentValues()
cv.put(COL_URL,url)
db.update(TABLE_NAME,cv, "id=?", arrayOf(id.toString())) >0
db.close()
}catch (e:Exception)
{
result= false
}
return true
}
fun readData():ArrayList<Tab>{
var list=ArrayList<Tab>()
var db=this.readableDatabase
val query="select * from" + TABLE_NAME + "ORDER BY id ASC"
val result=db.rawQuery(query,null)
if(result.moveToFirst())
{
do{
var tab = Tab()
tab.id=result.getInt(result.getColumnIndex(COL_ID).toInt())
tab.url=result.getString(result.getColumnIndex(COL_URL))
list.add(tab)
}while (result.moveToNext())
}
result.close()
db.close()
return list
}
fun deleteData(id:Int) {
val db=this.writableDatabase
db.delete(TABLE_NAME, COL_ID + "=" +id,null)
db.close()
}
I have the following LiveData variables in my ViewModel (simplified example):
val currentUser : LiveData<UserObject>
val allSites : LiveData<ArrayList<SiteObject>>
val filterSitesForUser : LiveData<Boolean>
val orderSitesByField : LiveData<String>
val orderSitesDirection : LiveData<Query.Direction>
val searchFilterSitesText : LiveData<String>
I'm trying to use MediatorLiveData to have one 'stream' of data connecting to my RecyclerView.
I therefore also have the following code in the ViewModel, which is observed in the Fragment:
fun sitesList() : LiveData<ArrayList<SiteObject>> {
val result = MediatorLiveData<ArrayList<SiteObject>>()
result.addSource(currentUser) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(allSites) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(filterSitesForUser) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(orderSitesByField) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(orderSitesDirection) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
result.addSource(searchFilterSitesText) {
result.value = combineSitesData(currentUser, allSites, filterSitesForUser, orderSitesByField, orderSitesDirection, searchFilterSitesText)
}
return result
}
..along with this, also in the ViewModel:
private fun combineSitesData (
currentUser: LiveData<UserObject>,
allSites: LiveData<ArrayList<SiteObject>>,
filterSitesForUser: LiveData<Boolean>,
orderSitesByField: LiveData<String>,
orderSitesDirection: LiveData<Query.Direction>,
searchFilterSitesText: LiveData<String>
) : ArrayList<SiteObject> {
var sitesList = ArrayList<SiteObject>()
val userId = currentUser.value?.userID
if (userId == null || allSites.value == null) {
Log.d(TAG, "combineSitesData() - currentUser or allSites value null")
return sitesList
}
when (filterSitesForUser.value) {
true -> sitesList.addAll(allSites.value!!.filter { site -> site.users.contains(userId) })
false -> sitesList.addAll(allSites.value!!)
}
if (orderSitesDirection.value == Query.Direction.ASCENDING){
when (orderSitesByField.value) {
DATE_CREATED -> sitesList.sortBy { it.dateCreatedTimestamp }
DATE_EDITED -> sitesList.sortBy { it.dateEditedTimestamp }
SITE_TASKS -> sitesList.sortBy { it.siteTask }
SITE_RATING -> sitesList.sortBy { it.siteRating }
else -> sitesList.sortBy {it.siteReference}
}
}
if (orderSitesDirection.value == Query.Direction.DESCENDING){
when (orderSitesByField.value) {
DATE_CREATED -> sitesList.sortByDescending { it.dateCreatedTimestamp }
DATE_EDITED -> sitesList.sortByDescending { it.dateEditedTimestamp }
SITE_TASKS -> sitesList.sortByDescending { it.siteTask }
SITE_RATING -> sitesList.sortByDescending { it.siteRating }
else -> sitesList.sortByDescending {it.siteReference}
}
}
if (!searchFilterSitesText.value.isNullOrEmpty()) {
var filteredList = ArrayList<SiteObject>()
var filterPattern = searchFilterSitesText.value.toString().toLowerCase().trim()
for (site in sitesList) {
if(site.siteReference.toLowerCase().contains(filterPattern) || site.siteAddress.toLowerCase().contains(filterPattern)) {
filteredList.add(site)
}
}
Log.d(TAG, "combineSitesData() - returned filteredList (size = ${filteredList.size})")
return filteredList
}
Log.d(TAG, "combineSitesData() - returned sitesList (size = ${sitesList.size})")
return sitesList
}
This is the code in the Fragment which observes the list of Sites:
// Observe ViewModel SitesList
businessViewModel.sitesList().observe(viewLifecycleOwner, Observer { sitesList ->
if (sitesList != null) {
businessViewModel.currentUser.value?.userID?.let { sitesAdapter.setList(it, sitesList) }
sitesAdapter.notifyDataSetChanged()
Log.d (TAG, "setupViewModelObservers(): businessViewModel.sitesList().size = ${sitesList.size}" )
}
})
It seems to be working apart from the fact that when it is first observes, it fires off six times, which therefore updates my RecylcerView six times - when this is a large list, I see this being a bit of an issue!
After it initialises, it only updates once when any of the LiveData's change, so that's ok.
I'm not sure how to prevent this from happening, without holding a list in the Fragment or ViewModel to compare observed list to, which I am trying to avoid (as trying to follow MVVM architecture)..
EDIT: for clarity, I have added my RecyclerViewAdapter to show what the function setList does:
class SitesRecyclerViewAdapter(
private var currentUserId: String,
private val onItemClickedListener: (SiteObject) -> Unit,
private val onItemLongClickedListener: (SiteObject) -> Boolean
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var sitesList = ArrayList<SiteObject>()
class SiteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val listItemBinding = SitesListItemBinding.bind(itemView)
fun bind(
userId: String,
site: SiteObject,
clickListener: (SiteObject) -> Unit,
longClickListener: (SiteObject) -> Boolean
) {
// Set text fields
listItemBinding.sitesItemTitleText.text = site.siteReference
// listItemBinding.sitesItemProjectsText.text = site.recentProjectsText TODO: Create way of showing recent projects with arrays
//Reset Icons visibility
listItemBinding.sitesItemTaskImageView.visibility = View.INVISIBLE
listItemBinding.sitesItemRating1ImageView.visibility = View.INVISIBLE
listItemBinding.sitesItemRating2ImageView.visibility = View.INVISIBLE
listItemBinding.sitesItemRating3ImageView.visibility = View.INVISIBLE
listItemBinding.sitesItemFavouriteImageView.visibility = View.GONE
//Set sitePriority Icon visibility
if (site.siteTask) listItemBinding.sitesItemTaskImageView.visibility = View.VISIBLE
//Set siteRating Icon visibility
when(site.siteRating){
1 -> listItemBinding.sitesItemRating1ImageView.visibility = View.VISIBLE
2 -> listItemBinding.sitesItemRating2ImageView.visibility = View.VISIBLE
3 -> listItemBinding.sitesItemRating3ImageView.visibility = View.VISIBLE
}
//Set siteFavourite Icon visibility
if (site.users.contains(userId)) listItemBinding.sitesItemFavouriteImageView.visibility = View.VISIBLE
// Set Listeners
listItemBinding.sitesItemCardview.setOnClickListener { clickListener(site) }
listItemBinding.sitesItemCardview.setOnLongClickListener { longClickListener(site) }
// Not set map listener?
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
// LayoutInflater: takes ID from layout defined in XML.
// Instantiates the layout XML into corresponding View objects.
// Use context from main app -> also supplies theme layout values!
val inflater = LayoutInflater.from(parent.context)
// Inflate XML. Last parameter: don't immediately attach new view to the parent view group
val view = inflater.inflate(R.layout.sites_list_item, parent, false)
return SiteViewHolder(view)
}
override fun getItemCount(): Int = sitesList.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
// Populate ViewHolder with data that corresponds to the position in the list
// which we are told to load
(holder as SiteViewHolder).bind(
currentUserId,
sitesList[position],
onItemClickedListener,
onItemLongClickedListener
)
}
fun setList(userID: String, observedSites: ArrayList<SiteObject>) {
currentUserId = userID
sitesList = observedSites
notifyDataSetChanged()
Log.d(TAG, "setList(), observedSites size = ${observedSites.size}")
}
}
You can use postValue instead of setValue
If you called this method multiple times before a main thread executed a posted task, only
the last value would be dispatched.