-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[android] add support to reuse instance with webViewKey #11
[android] add support to reuse instance with webViewKey #11
Conversation
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
Outdated
Show resolved
Hide resolved
} | ||
|
||
@ReactProp(name = "webViewKey") | ||
public void setWebViewKey(RNCWebView view, String webViewKey) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unlike iOS, where there is a didMoveToWindow
function, if there is a webViewKey, we rettach the view at this point
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
Outdated
Show resolved
Hide resolved
android/src/main/java/com/reactnativecommunity/webview/RNCWebView.java
Outdated
Show resolved
Hide resolved
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
Outdated
Show resolved
Hide resolved
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
Show resolved
Hide resolved
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
Outdated
Show resolved
Hide resolved
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
Outdated
Show resolved
Hide resolved
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewMapManager.kt
Outdated
Show resolved
Hide resolved
if (rncWebViewMap.containsKey(webViewKey)) { | ||
RNCWebView existingView = rncWebViewMap.get(webViewKey); | ||
|
||
InternalWebView existingWebView = existingView.detachWebView(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/discord/react-native-webview/blob/11.18.1-discord-1/apple/RNCWebViewMapManager.m
https://github.com/discord/react-native-webview/blob/11.18.1-discord-1/apple/RNCWKWebViewMapManager.m
In the iOS implementation, we have a map for both the outer view group RNCWebView
and also the inner WebView WKWebView
. If I'm remembering correctly, depending on how the JS for the react components was implemented, it was possible for the old RNCWebView
to get destroyed before the new one got created. In that case, we wouldn't be able to use the old RNCWebView
to get the old InternalWebView
via detachWebView
. Instead, we would have to grab the old InternalWebView
/ WKWebView
from a singleton map.
Is that possible on Android, too? In the test app, I think you can test this with the "toggle pages" button. That should let you test destroying the first RNCWebVIew
without immediately getting a second RNCWebView
. Then when you navigate away from the web view page to a different page and then back to the web view page, you should be able to still restore the old InternalWebView
. For that, you might need a map for InternalWebView
, but let me know what you find.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the test app, I think you can test this with the "toggle pages" button. That should let you test destroying the first RNCWebVIew without immediately getting a second RNCWebView. Then when you navigate away from the web view page to a different page and then back to the web view page, you should be able to still restore the old InternalWebView. For that, you might need a map for InternalWebView, but let me know what you find.
I am able to restore the old InternalWebView with Toggle Pages.
if I'm remembering correctly, depending on how the JS for the react components was implemented, it was possible for the old RNCWebView to get destroyed before the new one got created.
I am not sure how this is possible because this method is called
https://github.com/discord/react-native-webview/pull/11/files/de8f049d35b3911134285a514add9418eceec7ff#diff-e9700631fc9fa5600d2e1ee70eb2d28aa2a1fbc695b85f99981972d3904ca485R807
when the view is being destroyed. We are also maintaining a reference to this view inside the hashmap so it shouldn't be destroyed
I'll double check on iOS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are also maintaining a reference to this view inside the hashmap so it shouldn't be destroyed
I guess I was thinking that when the react component unmounts, we wouldn't hold onto the wrapper / RNCWebView, and instead, we'd only hold onto the InternalWebView / WKWebView. That way, we'd be holding on to only what's necessary without using too much memory (since it's fine if the RNCWebView
gets destroyed when the react component unmounts as long as the InternalWebView
stays alive).
In this code in the iOS RNCWebView.m
,
- (void)removeFromSuperview
{
bool keepWebViewInstance = _keepWebViewInstanceAfterUnmount && _webViewKey != nil;
if (!keepWebViewInstance) {
[self cleanUpWebView];
}
if (_webViewKey != nil) {
NSMutableDictionary *sharedRNCWebViewDictionary= [[RNCWebViewMapManager sharedManager] sharedRNCWebViewDictionary];
RNCWebView *rncWebView = sharedRNCWebViewDictionary[_webViewKey];
// When this view is being unmounted, only remove the WKWebView from the superview
// if this RNCWebView is the "active" view.
if (rncWebView == self) {
[self removeWKWebViewFromSuperView:self];
}
}
[super removeFromSuperview];
}
, we remove the RNCWebView
from the map when the react component unmounts. I think that part is different from the current Android implementation because it seems like we're keeping the Android RNCWebView
in the map when the component unmounts.
I'll double check on iOS
Sounds good! Let me know if you find anything different from what I described above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That way, we'd be holding on to only what's necessary without using too much memory (since it's fine if the RNCWebView gets destroyed when the react component unmounts as long as the InternalWebView stays alive).
I think the added memory here is marginal since it can be at most one wrapper view for each webViewKey. The wrapper view is also a single FrameLayout and I don't expect to see any reasonable performance gains by using another map.
I think the bigger question is consistency between iOS and Android - if there is no real need to maintain another map, I think it's easier to to maintain one map. iOS is slightly different in the sense that the RNCWebView is more complex so it might justify another map even though it may not be technically required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the bigger question is consistency between iOS and Android - if there is no real need to maintain another map, I think it's easier to to maintain one map. iOS is slightly different in the sense that the RNCWebView is more complex so it might justify another map even though it may not be technically required.
Makes sense. The consistency is valuable to me to make it easier for us to apply similar changes to both platforms. I'm leaning toward keeping the iOS and Android implementations more consistent. I think the easiest way to do that today would be to make Android have two maps (to match the current iOS implementation): one for the outer webview and one for the inner webview. Alternatively, I'm also open to us making iOS only have one map to match what this Android PR is doing if that doesn't create other issues on iOS.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree for consistency, given that iOS is somewhat battle-tested I'd leave that alone for now. I'll update Android and leave a comment that this is to remain consistent with iOS. We can do a pass later to remove it from both at some point (if needed?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
android/src/main/java/com/reactnativecommunity/webview/RNCWebView.java
Outdated
Show resolved
Hide resolved
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewManager.java
Outdated
Show resolved
Hide resolved
if (internalWebView != null) { | ||
action.apply(internalWebView); | ||
} else { | ||
FLog.e(TAG, new Throwable(), "Internal WebView is null"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added an empty throwable to get the stacktrace - not sure if this is the best way. I'd like the stacktrace so I can see what method was originally being called
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems appropriate to me
|
||
object RNCWebViewMapManager { | ||
val rncWebViewMap = mutableMapOf<String, RNCWebView>() | ||
val internalWebViewMap = mutableMapOf<String, WebView>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've used the type
WebView since Kotlin was complaining that the InternalWebView is a protected class and shouldn't be referenced here
@donaldchen I think most of your comments have been addressed - let me know if you find something new! |
if (internalWebView != null) { | ||
action.apply(internalWebView); | ||
} else { | ||
FLog.e(TAG, new Throwable(), "Internal WebView is null"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems appropriate to me
super.onDropViewInstance(view); | ||
|
||
// The internal webview can be null since the view may have been already reattached | ||
if (view.getWebView() == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Is this null-check and early-return necessary? view.ifHasInternalWebView
already does the null-check and then does nothing if it's null, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not necessary, it's more for explicitness i guess. if it's confusing I can remove it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way works for me! I'll leave it up to your preference
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewModule.java
Outdated
Show resolved
Hide resolved
android/src/main/java/com/reactnativecommunity/webview/RNCWebViewModule.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work! The high level changes look good to me 🎉 .
Before merging, it's worth double checking these small details:
#11 (comment)
and
#11 (comment)
This PR adds messaging passing. This was much simpler to do than the iOS counter part. This is the analogue to the following PR: #6. I've followed the same test plan. The sample app was really nice to use when testing. In the previous PR #11, I broke all native->react-native communication since events were being passed by the `InternalWebView` viewId not the `RNCWebView` viewId. This is now fixed.
…bView in view hierarchy * add support to reuse instance with webViewKey * change up * address comments related to layout params * add nullability checks * fix * get full stack trace * add secondary map * rename to ifHasInternalWebView * additional fixes * fix * fix boolean [android] add messaging passing without a react tag (#12) This PR adds messaging passing. This was much simpler to do than the iOS counter part. This is the analogue to the following PR: #6. I've followed the same test plan. The sample app was really nice to use when testing. In the previous PR #11, I broke all native->react-native communication since events were being passed by the `InternalWebView` viewId not the `RNCWebView` viewId. This is now fixed. [android] check source prop if changed (#14) * [android] check source prop if changed * detect if new source is null too
…bView in view hierarchy * add support to reuse instance with webViewKey * change up * address comments related to layout params * add nullability checks * fix * get full stack trace * add secondary map * rename to ifHasInternalWebView * additional fixes * fix * fix boolean [android] add messaging passing without a react tag (#12) This PR adds messaging passing. This was much simpler to do than the iOS counter part. This is the analogue to the following PR: #6. I've followed the same test plan. The sample app was really nice to use when testing. In the previous PR #11, I broke all native->react-native communication since events were being passed by the `InternalWebView` viewId not the `RNCWebView` viewId. This is now fixed. [android] check source prop if changed (#14) * [android] check source prop if changed * detect if new source is null too
…bView in view hierarchy * add support to reuse instance with webViewKey * change up * address comments related to layout params * add nullability checks * fix * get full stack trace * add secondary map * rename to ifHasInternalWebView * additional fixes * fix * fix boolean [android] add messaging passing without a react tag (#12) This PR adds messaging passing. This was much simpler to do than the iOS counter part. This is the analogue to the following PR: #6. I've followed the same test plan. The sample app was really nice to use when testing. In the previous PR #11, I broke all native->react-native communication since events were being passed by the `InternalWebView` viewId not the `RNCWebView` viewId. This is now fixed. [android] check source prop if changed (#14) * [android] check source prop if changed * detect if new source is null too
Adds support to re-use the same native webview
InternalWebView
and the name for the wrapper class isRNCWebView
android-reuse-webview.mov