I'm having issues with a Native UI Component that is used in React-Native. The situation is that I have an existing, functioning, completed layout in Android that I need to reuse in a react-native view.
The existing layout (in Android):
has a loading-layout
has a RecyclerView
does api-calls while showing the loading-layout
once done it updates the RecyclerView and hides the loading-layout
works perfectly in a native environment.
To expose this existing layout and use it in React-Native I did as told
by the docs. Exposing properties, callbacks or events was not required as the existing layout is closed and works as is.
The React-Native view:
is a simple view
that imports the existing layout as described by the docs
actually shows the initial loading-layout of the existing layout
The problem occurs when the data is loaded. With debugging you can see that the code is run to update the layout, but that is visually not shown. The loading-layout keeps showing, until you perform a scroll-gesture which redraws the screen and shows the RecyclerView with the loaded data. As if the RecyclerView was already there but just not showing.
Would anybody know what could be the problem?
Thanks is advance!
try adding this function, and change base_svg to the iew that you disered :
private final Runnable measureAndLayout = new Runnable() {
#Override
public void run() {
base_svg.measure(
View.MeasureSpec.makeMeasureSpec(base_svg.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(base_svg.getHeight(), View.MeasureSpec.EXACTLY));
base_svg.layout(base_svg.getLeft(), base_svg.getTop(), base_svg.getRight(), base_svg.getBottom());
}
};
After you add the new information to the screen call that function this way:
base_svg.post(measureAndLayout);
Check if it renders the new views that you add or remove.
To be honest I don't know the reason why. but this work for me. I think react is forcing you to just to render when react wants.
Check this issue for more understanding.
Related
My outer RecyclerView crashes either with
IllegalArgumentException: Scrapped or attached views may not be recycled. isScrap:false isAttached:true...
or
IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
Like the title suggests I have an RecyclerView in the list item layout of the first RecyclerView. This layout is used to display messages and the
inner RecyclerView to display attachments that come with the message. The inner RecyclerViews visibility is set to either GONE or VISIBLE depending whether the message has any attachments or not. The simplified outer list item layout looks like this
ConstraintLayout
TextView
TextView
TextView
RecyclerView
And the part of the adapter that handles the inner RecyclerView looks like this
private fun bindFiles(message: Message?) = with(itemView) {
if (message != null && message.attachments.isNotEmpty())
{
sent_message_attachments.setAsVisible()
sent_message_attachments.layoutManager = GridLayoutManager(this.context,Math.min(message.attachments.size,3))
sent_message_attachments.adapter = AttachmentAdapter(message.attachments)
sent_message_attachments.itemAnimator = null
sent_message_attachments.setHasFixedSize(true)
}
else{
sent_message_attachments.setAsGone()
sent_message_attachments.adapter = null
sent_message_attachments.layoutManager = null
}
}
The bug has something to do with the way I fetch the attachments in the inner adapter since once I disable the part that start the download process, everything is fine. There's no problem when loading images from the device, but once I start the download process, everything goes to hell. This is the part that handles images and kicks off the download process in the inner adapter. I have functions for videos and for other file types that are pretty much the same exact thing but use slightly different layout.
private fun bindImage(item: HFile?) = with(itemView) {
if (item != null)
{
if (item.isOnDevice && !item.path.isNullOrEmpty())
{
if (item.isGif)
{
attachment_image.displayGif(File(item.path))
}
else
{
attachment_image.displayImage(File(item.path))
}
}
else
{
//TODO: Add option to load images manually
FileHandler(item.id).downloadFileAsObservable(false)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ progress ->
//TODO: Show download process
},
{ error ->
error.printStackTrace()
//TODO: Enable manual retry
},
{ notifyItemChanged(adapterPosition)} //onComplete
)
}
}
}
I use the same structure as above in my DiscussionListAdapter to load discussion portraits (profile pictures etc.) and it does not have the same issue.
These are the extensions functions used to inflate the viewHolders and to display the images
fun ViewGroup.inflate(layoutRes: Int): View
{
return LayoutInflater.from(context).inflate(layoutRes, this, false)
}
fun ImageView.displayGif(file:File){
GlideApp.with(context).asGif().load(file).transforms(CenterCrop(), RoundedCorners(30)).into(this)
}
fun ImageView.displayImage(file:File){
GlideApp.with(context).load(file).transforms(CenterCrop(), RoundedCorners(30)).into(this)
}
I've been on this for the past couple of days and just can't get my head around it. Any help in any direction is greatly appreciated. I know my explanations can be a bit all over the place so just ask for clarification when needed :)
UPDATE
I have now been able to produce this with a GridLayout as well as with RecyclerView. It's safe to assume that the nested RecyclerViews were not the culprit here. I even tried to ditch the Rx-piece that handled loading the images and created an IntentService for the process, but the same crashes still occur.
With GridLayout I mean that instead of having another adapter to populate the nested RecyclerView I use only one adapter to populate the message and to inflate and populate views for the attachments as well and to attach those views to the nested GridLayout.
The crash happens when I start to download a file and then scroll the view, that is supposed to show the downloaded file, out of the screen. That view should get recycled but for some reason the download process (which in my test cases only takes around 100ms-400ms) causes the app to throw one of the two errors mentioned in the original question. It might be worth noting that I'm using Realm and the adapter takes in a RealmResults<Message> list as it's dataset. My presenter looks for changes in the list and then notifies the adapter when needed (changed due to the implementation of IntentService).
This is how I'm capable to reproduce this time and time again:
Open a discussion that has messages with attachments
Start to scroll upwards for more messages
Pass a message with an attachment and scroll it off screen while it's still loading
Crash
There is no crash if I stop and wait for the download to complete and everything works as intended. The image/video/file gets updated with a proper thumbnail and the app wont crash if I scroll that out of view.
UPDATE 2
I tried swapping the nested ViewGroup for a single ImageView just to see is the problem within the nestedness. Lo and behold! It still crashes. Now I'm really confused, since the DiscussionListAdapter I mentioned before has the same exact thing in it and that one works like a charm... My search continues. I hope someone, some day will benefit from my agony.
UPDATE 3
I started to log the parent of every ViewHolder in the onBindViewHolder() function. Like expected I got nulls after nulls after nulls, before the app crashed and spew this out.
04-26 21:54:50.718 27075-27075/com.hailer.hailer.dev D/MsgAdapter: Parent of ViewHolder: android.view.ViewOverlay$OverlayViewGroup{82a9fbc V.E...... .......D 0,0-1440,2168}
There's a method to my madness after all! But this just poses more questions. Why is ViewOverlay used here? As a part of RecyclerView or as a part of the dark magicians plans to deprive me of my sanity?
Sidenote
I went digging into RecyclerViews code to check if I could find a reason for the ViewOverlaymystery. I found out that RecyclerView calls the adapters onCreateViewHolder() function only twice. Both times providing itself as the parent argument for the function. So no luck there... What the hell can cause the item view to have the ViewOverlay as it's parent? The parent is an immutable value, so the only way for the ViewOverlay to be set as the parent, is for something to construct a new ViewHolder and supply the ViewOverlay as the parent object.
UPDATE 4
Sometimes I amaze myself with my own stupidity. The ViewOverlay is used because the items are being animated. I didn't even consider this to be an option since I've set the itemAnimator for the RecyclerView as null, but for some odd reason that does not work. The items are still being animated and that is causing this whole charade. So what could be the cause of this? (How I chose to ignore the moving items, I do not know, but the animations became very clear when I forced the app to download same picture over and over again and the whole list went haywire.)
My DiscussionInstanceFragment contains the RecyclerView in question and a nested ConstraintLayout that in turn contains an EditText for user input and a send button.
val v = inflater.inflate(R.layout.fragment_discussion_instance, container, false)
val lm = LinearLayoutManager(context)
lm.reverseLayout = true
v.disc_instance_messages_list.layoutManager = lm
v.disc_instance_messages_list.itemAnimator = null
v.disc_instance_messages_list.adapter = mPresenter.messageAdapter
This is the piece that handles the initialization of the RecyclerView. I'm most definitely setting the itemAnimator as null, but the animations just wont stop! I've tried setting the animateLayoutChanges xml attribute on the root ConstraintLayout and on the RecyclerView but neither of them worked. It's worth mentioning that I also checked whether the RecyclerView had an itemAnimator in different states of the program, and every time I check the animator, it is null. So what is animating my RecyclerView?!
I have faced the same issue
Try this in your child RecyclerView it works for me
RecyclerView childRC = itemView.findViewById(R.id.cmol_childRC);
layoutManager = new LinearLayoutManager(context);
childRC.setItemAnimator(null);
childRC.setLayoutManager(layoutManager);
childRC.setNestedScrollingEnabled(false);
childRC.setHasFixedSize(true);
now set your Adapter like this
ArrayList<Model> childArryList = new ArrayList<>();
childArryList.addAll(arrayList.get(position).getArrayList());
ChildOrderAdapter adapter = new ChildOrderAdapter(context, childArryList);
holder.childRC.swapAdapter(adapter, true);
hope this helps
I finally figured out what was causing this. In my DiscussionInstanceView I have a small view that is animated into and out of view with ConstraintLayout keyframe animations. This view only shows the download progress of the chat history and is used only once, when the discussion is first opened. BUT since I had a call to hiding that view every time my dataset got updated, I was forcing the ConstraintLayout to fire of an animation sequence thus making everything animate during the dataset update. I just added a simple check whether I was downloading the history or not and this problem got fixed.
I am working on a Xamarin.Android app. I have a ListView that has the following code:
OnCreate
{
//other code here
listView.Scroll += ListView_Scroll;
}
private void ListView_Scroll(object sender, AbsListView.ScrollEventArgs e)
{
throw new NotImplementedException();
}
(I simply added the Scroll += ... and the Empty Project to the Custom Row Views project of Xamarin Android.
Immediately when the list is loaded, it throws the NotImplementedException. Even when Adapter is null, it still scrolls!
Why does it scroll when it does not need to? The ScrollState also changes to Fling.
Can someone explain why this happens? I am working on an app that uses this event and this is very annoying to work around.
I don't know if this also happens in Android but I assume it does, that's why I have tagged Android, as well.
ListView in Android doesn't inherit from Scrollview but is very similar to the same
The list-view module is using android.widget.ListView for its Android implementation while behind ScrollView lies android.widget.HorizontalScrolView (or vertical) so basically we have two different native controls with different implementations. Comparing to HorizontalScrollView, the ListView for Android presets much more functionality for listing data and also has specific optimisations so this is the logical choice to implement in nativeScript as well.
Secondly it throws that exception because you have the following line in your scroll event :
throw new NotImplementedException();
Removing this should solve your problem for the most part.
Why does it scroll when it does not need to?
What actually happens here is that the listview Scrolls To the current Position
Goodluck Happy Coding!
The Test - I have an Espresso test suite designed to test some complex decision making with a RecyclerView adapter.
For one test I intend to create a RecyclerView, pass it the adapter under test and then determine that the correct number of children exist, with the correct elements being visible.
The problem - I am testing this in isolation without a specific Activity, which means that the following code never passes, because the RecyclerView never lays out its children:
RecyclerView rv = new RecyclerView(InstrumentationRegistry.getTargetContext());
rv.setAdapter(adap);
assertThat(rv.getChildCount(), is(greaterThan(0)));
I assume I could make this work by creating an empty activity and an empty layout with the RecyclerView in, but i want to avoid creating garbage classes like that.
The Question - Is it possible to get the RecyclerView to function alone like this? Does Espresso have some root view I can access to attach it to?
As noted by CommonsWare, the way to ensure RecyclerView is ready for testing without an activity is to force layout yourself.
RecyclerView rv = new RecyclerView(InstrumentationRegistry.getTargetContext());
rv.setLayoutManager(new LinearLayoutManager(InstrumentationRegistry.getTargetContext()));
rv.setAdapter(adapter);
rv.measure(View.MeasureSpec.AT_MOST, View.MeasureSpec.AT_MOST);
rv.layout(0,0, 800, 480); //arbitrary sizes
After creating your view in this manner, the child count will no longer be zero
assertThat(rv.getChildCount(), is(greaterThan(0)));
The one caveat to testing the RecyclerView without an Activity is you are unable to perform actions.
onView(...).perform(click());
Without an activity this code will throw a RuntimeException with the message
No activities found. Did you forget to launch the activity by calling getActivity() or startActivitySync or similar?
I am automating test cases for android app using Appium (java).
I am stuck on an interesting scenario:
My test case is to change the view of products by clicking on an icon.
That's done.
Now I need to verify that view has been changed or not, here is where the actual problem comes.
In both the cases the parent class is android.widget.GridView and subclass is android.widget.RelativeLayout - only the the way they appear on screen is changed (First they appeared in list type and later in grid type).
I am using UIAutomator for UI elements.How to detect this change?
Any help/ suggestion /work around would be greatful
UPDATE I am attaching the screenshots of UI Automator.
You know the result view what you going to get. Just verify if that element is visible or not.
List<WebElement> view = driver.findElements(By.className("android.view.View"));
System.out.println(view);
if (view.size() > 0 || view != null) {
viewFound = true;
}
I am trying to test my app which uses ViewPager. Each page contains fragments but these fragments are not always visible. I want to check visibility of a fragment in the currently visible page.
onView(withId(R.id.container_weather))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
But the problem is that espresso looks are all the pages not just the current page and I get the following error:
android.support.test.espresso.AmbiguousViewMatcherException: 'with id: eu.airpatrol.android:id/container_weather' matches multiple views in the hierarchy...
I had the same problem, however using the condition isCompletelyDisplayed() solved this problem as it only takes into account the on-screen views.
So, something like this should work:
onView(allOf(withId(R.id.container_weather), isCompletelyDisplayed()))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
Note: isDisplayed() works too in some cases but it also takes views off-screen into account and won't work if the ViewPager has any other page pr fragment loaded with the same view id.
Your tests are failing because of multiple elements with the same id. You can combine conditions using allOf(...). Then use isDisplayed() to check that matched view is visible on the screen. Below example can work:
onView(allOf(
withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
withId(R.id.container_weather)))
.check(matches(isDisplayed()));
Ran into this exact same problem. I was fortunate because the view hierarchies in my ViewPager can be easily identified by their siblings, so I was able to solve this using the hasSibling matcher, like so:
onView(
allOf(
hasSibling(withId(R.id.some_sibling)),
withId(R.id.field_to_test)
)
).perform(replaceText("123"));
Not a perfect solution as it can be slightly brittle, but in my case I think it was an acceptable compromise.
I had similar problem, where I was reusing the button layout and it was giving me a matches multiple views in the hierarchy exception.
So the easy work around I did was to create 2 different screens and have 2 different methods with different text.
Withdraw Screen:
public WithdrawScreen clickWithdraw() {
onView(allOf(withId(R.id.save_button), withText("Withdraw")))
.perform(click());
return this;
}
Deposit Screen:
public DepositScreen clickDeposit() {
onView(allOf(withId(R.id.save_button), withText("Deposit")))
.perform(click());
return this;
}
and in my tests, I create a new instance of both screens and call the above methods based on screen reference which is a bit easy to test for.
WithdrawScreen withdrawInstance = new WithdrawScreen();
withdrawInstance.clickWithdraw();
DepositScreen depositInstance = new DepositScreen();
depositInstance.clickDeposit();
The point was they were using same id - R.id.save_button for button and I was replacing text of button based on visibility of the fragment we are on.
Hope it helps.