I am trying to update the recyclerView adapter in the observe response of live data object but it is not updating the UI rather if I debug it, it starts updating the UI.
Looks like adding a delay make it work or reassigning the recyclerView items adapter also works but I didn't understand why is the notifyDataSetChanged or notifyItemRangeChanged is not working
following is the code I am using
adapter = MyAdapter(listOf())
binding.recyclerView.adapter = adapter
viewModel = ViewModelProvider(this).get(HomeViewModel::class.java)
viewModel.data.observe(this, {
adapter.setData(it)
// This updates the UI, but this is not the right way to do so
//binding.recyclerViewData.adapter = adapter
})
Adapter class where I update the data
fun setData(data: MutableList<DataModelRoom>) {
this.data= data
notifyItemRangeChanged(0, data.size)
}
ViewModel part of the code
var data: MutableLiveData<MutableList<DataModel>> = MutableLiveData()
/* This is part of init method */
viewModelScope.launch {
Amplify.Hub.subscribe(HubChannel.DATASTORE) { event ->
if (event.name == DataStoreChannelEventName.READY.toString()) {
isAmplifyDataReady.postValue(true)
data.postValue(repository.getDataFromAmplify())
}
Log.i(Logging.TAG_AMPLIFY, "event: $event")
}
/* Starting the DataStore Syncing */
Amplify.DataStore.start(
{ Log.i(Logging.TAG_AMPLIFY, "DataStore started") },
{ Log.e(Logging.TAG_AMPLIFY, "Error starting DataStore", it) }
)
}
Layout part of the recyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="152dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textViewView" />
I think you're over complicating the issue. Use a ListAdapter<T, K> (included with the platform and call adapter.submitList(pass_the_new_list_here) and have the (required) DiffUtil.Callback handle the differences and update what's needed.
Related
Please i have been trying this for weeks now. I have a fragment that displays notifications. But it time i click on the notification my app crashes. it points me the line below Picasso.get().load(user!!.getImage()).placeholder(R.drawable.profile).into(imageView)
and it says: java.lang.IllegalArgumentException: Path must not be empty
From logcat, using picasso seems to be the problem but i am new to programming and i have no idea what exacty to do. Please help me. Thank you
Below is the complete code for my notificationAdapter
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(mContext).inflate(R.layout.notifications_item_layout , parent,false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val notification = mNotification[position]
if(notification.getText().equals("Admirers you")){
holder.text.text = "Admirers you"
}
else if(notification.getText().equals("Liked your post")){
holder.text.text = "Liked your post"
}
else if (notification.getText().contains("commented:")){
holder.text.text = notification.getText().replace("commented:", "commented: ")
}
else{
holder.text.text = notification.getText()
}
userInfo(holder.profileImage, holder.fullname, notification.getUserId())
if(notification.isIsPost()){
holder.postImage.visibility = View.VISIBLE
getPostImage(holder.postImage, notification.getPostId())
}
else{
holder.postImage.visibility = View.GONE
}
holder.itemView.setOnClickListener {
if(notification.isIsPost()){
val editor = mContext.getSharedPreferences("PREFS", Context.MODE_PRIVATE).edit()
editor.putString("postId", notification.getPostId())
editor.apply()
(mContext as FragmentActivity).getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, PostDetailsFragment()).commit()
}
else{
val editor = mContext.getSharedPreferences("PREFS", Context.MODE_PRIVATE).edit()
editor.putString("profileId", notification.getUserId())
editor.apply()
(mContext as FragmentActivity).getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, ProfileFragment()).commit()
}
}
}
override fun getItemCount(): Int {
return mNotification.size
}
inner class ViewHolder(#NonNull itemView: View) : RecyclerView.ViewHolder(itemView)
{
var postImage: ImageView
var profileImage : CircleImageView
var fullname : TextView
var text : TextView
init {
postImage = itemView.findViewById(R.id.notification_post_image)
profileImage = itemView.findViewById(R.id.notifications_profile_image)
fullname = itemView.findViewById(R.id.fullname_notification)
text = itemView.findViewById(R.id.comment_notification)
}
}
private fun userInfo(imageView: ImageView, fullname: TextView, publisherId:String)
{
val usersRef =
FirebaseDatabase.getInstance().reference
.child("Users")
.child(publisherId)
usersRef.addValueEventListener(object : ValueEventListener
{
override fun onDataChange(p0: DataSnapshot)
{
if (p0.exists())
{
val user = p0.getValue(User::class.java)
Picasso.get().load(user!!.getImage()).placeholder(R.drawable.profile).into(imageView)
fullname.text = user.getfullname()
}
}
override fun onCancelled(p0: DatabaseError) {
}
})
}
private fun getPostImage(imageView: ImageView, postID:String)
{
val postRef =
FirebaseDatabase.getInstance()
.reference.child("Posts")
.child(postID)
postRef.addValueEventListener(object : ValueEventListener
{
override fun onDataChange(p0: DataSnapshot)
{
if (p0.exists()) {
val post = p0.getValue<Post>(Post::class.java)
Picasso.get().load(post!!.getpostimage()).placeholder(R.drawable.profile)
.into(imageView)
}
}
override fun onCancelled(p0: DatabaseError) {
}
})
}
}
And also my notification fragment
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="Fragments.NotificationsFragment">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/app_bar_layout_notifications"
android:background="#color/white">
<androidx.appcompat.widget.Toolbar
android:id="#+id/notifications_toolbar"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="4dp"
android:layout_marginLeft="6dp"
android:layout_marginRight="6dp"
android:background="#android:color/white">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Notifications"
android:textSize="18sp"
android:maxLines="1"
android:textStyle="bold"
android:textColor="#android:color/black"
android:layout_centerVertical="true"/>
</RelativeLayout>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view_notifications"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:layout_below="#+id/app_bar_layout_notifications">
</androidx.recyclerview.widget.RecyclerView>
Then in case you need more info, here's my notifications item layout. Thank you
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="6dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/notifications_profile_image"
android:layout_width="60dp"
android:layout_height="60dp"
android:src="#drawable/profile">
</de.hdodenhof.circleimageview.CircleImageView>
<LinearLayout
android:layout_toEndOf="#+id/notifications_profile_image"
android:layout_toStartOf="#+id/notification_post_image"
android:layout_centerVertical="true"
android:layout_marginStart="5dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/fullname_notification"
android:textStyle="bold"
android:textColor="#color/teal_200"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
<TextView
android:id="#+id/comment_notification"
android:textStyle="bold"
android:textColor="#color/teal_200"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
</LinearLayout>
<ImageView
android:id="#+id/notification_post_image"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentEnd="true">
</ImageView>
I don't know Picasso, so I can't tell you what the problem is*. (I don't even know Android, which I guess this is for.) But since you're new to programming, it seems to me that what you need to know is how to find out the problem for yourself — how to debug — so here are some hints that I hope will help.
You've done the right thing in starting with the logs; if you have an exception with a stack trace, that's always helpful in narrowing down the problem. But your case hasn't narrowed it down enough, because that one line is doing many different things. So the simplest thing to try would be to split that line up — then the stack trace's line number will narrow it down more finely.
You could simply wrap the line, e.g.:
Picasso.get()
.load(user!!.getImage())
.placeholder(R.drawable.profile)
.into(imageView)
Then you'll be able to tell which of those main operations triggered the exception. (Of course, the operation that gave the exception might not be the one you need to change — but either way you need to understand the problem before you can fix it.)
If that's not enough, then there are several approaches you could take. But the one I usually end up falling back on is the most general: logging. Whether you're working on a desktop app, a mobile app, a web app, a microservice, a stand-alone app, or whatever, there's almost always a way to print out some text in such a way that you can see it on screen or in a file. As I said, I don't know Android, but this question seems to show how you can do that; in other environments, you might use one of several logging libraries (java.util.logging, log4j…), or just the basic println(). But however you do it, there's bound to be a way you can see some of the intermediate values — and being able to see what's going on is always valuable when debugging.
You could try printing out the user (or, if that doesn't have a simple string representation, their name or something else that might give you a clue). Then the image, ditto. The the results of the load() call. And so on. Of course, that means restructuring your code a bit, e.g.:
val picasso = Picasso.get()
println("picasso = $picasso")
println("user = $user")
val image = user!!.getImage()
println("image = $image")
val loaded = picasso.load(image)
println("loaded = $loaded")
println("profile = ${R.drawable.profile}")
val placeholder = loaded.placeholder(R.drawable.profile)
println("placeholder = $placeholder")
println("imageView = $imageView")
placeholder.into(imageView)
(Substituting whatever logging method works in your case. I'm overdoing it here, of course. But I hope you get the idea.)
For the record, Kotlin's also() gives a way of doing the above without having to name all the temporary variables, or split up the chain of calls:
Picasso.get()
.also{ println("picasso = $it") }
.also{ println("user = $user") }
.load(user!!.getImage().also{ println("image = $it") } )
.also{ println("loaded = $it") }
.placeholder(R.drawable.profile.also{ println("profile = $it") })
.also{ println("placeholder = $it") }
.into(imageView.also{ println("imageView = $it") })
However, that can get unwieldy pretty quickly, so I wouldn't normally recommend it.
Either way, in all likelihood one of the values you print out won't be what you expect, or you'll be able to spot something that doesn't look right, and that will give you enough info to identify the problem — or at least, to direct your attention to some other part of the code that you can then debug in the same way.
Once you've fixed the problem and the code is behaving as expected, you can remove the logging. However, it's often better to leave some logging code in. (Especially if you're using a logging library that lets you set different levels, so you can log this at a low level that you won't normally get to see.) Then, when you need to track down some other problem in the code, it's much easier to see what's going on.
(* From the exception message, I'd suspect an issue with the user!!.getImage() part. But that's just a blind guess.)
I have read the article. I know the following content just like Image B.
Warning: Never collect a flow from the UI directly from launch or the launchIn extension function if the UI needs to be updated. These functions process events even when the view is not visible. This behavior can lead to app crashes. To avoid that, use the repeatOnLifecycle API as shown above.
But the Code A can work well without wrapped with repeatOnLifecycle, why?
Code A
#Composable
fun Greeting(handleMeter: HandleMeter,lifecycleScope: LifecycleCoroutineScope) {
Column(
modifier = Modifier.fillMaxSize()
) {
var my by remember { mutableStateOf(5)}
Text(text = "OK ${my}")
var dataInfo = remember { handleMeter.uiState }
lifecycleScope.launch {
dataInfo.collect { my=dataInfo.value }
}
}
class HandleMeter: ViewModel() {
val uiState = MutableStateFlow<Int>(0)
...
}
Image B
Code A will not work in real life. If you need to run some non-UI code in a composable function, use callbacks (like onClick) or LaunchedEffect (or other side effects).
LaunchedEffect {
dataInfo.collect {my=dataInfo.value}
}
Side effects are bound to composables, there is no need to specify the owner of their lifecycle directly.
Also, you can easily convert any flow to state:
val my = handleMeter.uiState.collectAsState()
When instantiating the ListAdapter for my RecyclerView, I call:
viewModel.currentList.observe(viewLifecycleOwner){
adapter.submitList(it)
}
which is what I've seen done in the Android Sunflower project as the proper way to submit LiveData to a RecyclerView. This works fine, and when I add items to my database they are updated in the RecyclerView. However, if I write
viewModel.currentList.observe(viewLifecycleOwner){
adapter.submitList(it)
adapter.notifyDataSetChanged()
}
Then my adapters data observer always lags behind. I call
adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver(){
override fun onChanged() {
super.onChanged()
if (adapter.currentList.isEmpty()) {
empty_dataset_text_view.visibility = View.VISIBLE
} else {
empty_dataset_text_view.visibility = View.GONE
}
}
})
And empty_dataset_text_view appears one change after the list size reached zero, and likewise it disappears one change after the list size is non-zero.
Clearly, the observer is running the code before it has submitted it which in this case is LiveData queried from Room. I'd simply like to know what the workaround is for this: is there a way to "await" a return from my LiveData?
my MainActivity contains a ViewPager that loads 4 fragments, each fragment should load lots of data from the server.
so when my app wants to be run for the first time, it almost takes more than 3 seconds and the other times(for example, if you exit the app but not clean it from your 'recently app' window and reopen it) it takes almost 1 second.
while it is loading, it shows a white screen.
is there any way instead of showing a white screen till data become ready, I show my own image?
something like the splash page?
If you do long-running actions on the main thread, you risk getting an ANR crash.
Your layout for each fragment should have a loading view that is initially visible, and your data view. Something like this:
(not code)
FrameLayout
loading_view (can show a progress spinner or something, size is match parent)
content_view (probably a RecyclerView, initial visibility=GONE, size is match parent)
/FrameLayout
You need to do your long running action on a background thread or coroutine, and then swap the visibility of these two views when the data is ready to show in the UI.
You should not be directly handling the loading of data in your Fragment code, as Fragment is a UI controller. The Android Jetpack libraries provide the ViewModel class for this purpose. You would set up your ViewModel something like this. In this example, MyData could be anything. In your case it's likely a List or Set of something.
class MyBigDataViewModel(application: Application): AndroidViewModel(application) {
private val _myBigLiveData = MutableLiveData<MyData>()
val myBigLiveData: LiveData<MyData>() = _myBigLiveData
init {
loadMyBigData()
}
private fun loadMyBigData() {
viewModelScope.launch { // start a coroutine in the main UI thread
val myData: MyData = withContext(Dispatchers.Default) {
// code in this block is done on background coroutine
// Calculate MyData here and return it from lambda
// If you have a big for-loop, you might want to call yield()
// inside the loop to allow this job to be cancelled early if
// the Activity is closed before loading was finished.
//...
return#withContext calculatedData
}
// LiveData can only be accessed from the main UI thread so
// we do it outside the withContext block
_myBigLiveData.value = myData
}
}
}
Then in your fragment, you observe the live data to update the UI when it is ready. The below uses the fragment-ktx library, which you need to add to your project. You definitely should read the documentation on ViewModel.
class MyFragment: Fragment() {
// ViewModels should not be instantiated directly, or they won't be scoped to the
// UI life cycle correctly. The activityViewModels delegate handles instantiation for us.
private val model: MyBigDataViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.myBigLiveData.observe(this, Observer<MyData> { myData ->
loading_view.visibility = View.GONE
content_view.visibility = View.VISIBLE
// use myData to update the view content
})
}
}
I'm creating a costum Android Lint Inspection and I need to register the inspection, to be run. Where do I need to register it?
I've already tried to register the inspection which provides the inspection inside plugin.xml file.
The actual inspection:
class HardcodedDimensionsInspection : AndroidLintInspectionBase("Hardcoded dimensions", HardcodedDimensDetector.ISSUE) {
override fun getShortName(): String {
return "AndroidLintHardcodedDimension"
}
}
The entry in plugin.xml file
<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
<!-- <inspectionToolProvider implementation="JavaInspectionProvider"/>-->
<globalInspection shortName="AndroidLintHardcodedDimension" displayName="Hardcoded dimensions"
enabledByDefault="true" level="WARNING"
implementationClass="HardcodedDimensionsInspection"/>
</extensions>
The actual detector
class HardcodedDimensDetector : LayoutDetector() {
override fun getApplicableAttributes(): Collection<String>? {
return Arrays.asList(
// Layouts
ATTR_TEXT
)
}
override fun appliesTo(folderType: ResourceFolderType): Boolean {
return (folderType == ResourceFolderType.LAYOUT ||
folderType == ResourceFolderType.MENU ||
folderType == ResourceFolderType.XML)
}
override fun visitAttribute(context: XmlContext, attribute: Attr) {
val value = attribute.value
}
companion object {
/** The main issue discovered by this detector */
#JvmField
val ISSUE = Issue.create(
id = "HardcodedDimension",
briefDescription = "Hardcoded dimens",
explanation = """
Brief
""",
category = Category.I18N,
priority = 5,
severity = Severity.ERROR,
implementation = Implementation(
HardcodedDimensDetector::class.java,
Scope.RESOURCE_FILE_SCOPE
)
)
}
}
I've expected to hit the breakpoints in any of the functions for Detector but the code is never called. Seems like my detector is not registered. Can you please point me to the missing part, is there a class where I should register my Detector?
Thank you.
The link to the full project: https://github.com/magicbytes/Android-Lint-Inspection
I don't see anything obvious wrong from these snippets. Could you please post on our forum and link to the full sources of your plugin? Thanks. https://intellij-support.jetbrains.com/hc/en-us/community/topics/200366979-IntelliJ-IDEA-Open-API-and-Plugin-Development
I have a workaround for now, not sure it's the official way to do it. Android Lint has a registry with all the Issue classes (built-in), the class is called LintIdeIssueRegistry. When it runs the Android Lint, it's looking in this registry for Issue processors. Since the list is hardcoded, we need to inject ours in the list. I'm using the following code for that:
val registry = LintIdeIssueRegistry()
val issue = registry.getIssue(HardcodedDimensDetector.ISSUE.id)
if (issue == null) {
val list = registry.issues as MutableList<Issue>
list.add(HardcodedDimensDetector.ISSUE)
}
Hopefully in future we will have a method called addIssue inside the LintIdeIssueRegistry.