The MVP pattern. Who are you, WebView-View or Model?
Hello everyone, I'm building an MVP architecture in my project and I have problems in WebView
. Look carefully at this class.
public class LoginWebView extends WebView
{
public LoginWebView(Context ctx)
{
this(ctx, null);
}
public LoginWebView(Context ctx, AttributeSet attrs)
{
super(ctx, attrs);
getSettings().setDomStorageEnabled(true);
getSettings().setJavaScriptEnabled(true);
getSettings().setLoadsImagesAutomatically(false);
}
public void authorize(LoginCallback callback, GameProvider provider, String login, String password)
{
setWebViewClient(new LoginWebViewClient(callback, provider, login, password));
setWebChromeClient(new WebChromeClient());
loadUrl(provider.getLoginPage());
}
class LoginWebViewClient extends WebViewClient
{
private LoginCallback callback;
private GameProvider provider;
private String login, password;
private boolean jsInjected;
public LoginWebViewClient(LoginCallback callback, GameProvider provider, String login, String password)
{
this.callback = callback;
this.provider = provider;
this.login = login;
this.password = password;
}
@Override
public void onPageFinished(WebView view, String url)
{
super.onPageFinished(view, url);
if (!jsInjected)
{
jsInjected = true;
Toast.makeText(getContext(), "Login script...", 1).show();
loadUrl(provider.getLoginScript(login, password));
}
else
{
setWebViewClient(new WebViewClient());
setWebChromeClient(new LoginWebChromeClient(callback, provider));
loadUrl("javascript:(function() { alert(document.documentElement.outerHTML); })()");
}
}
@Override
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error)
{
setWebViewClient(new WebViewClient());
callback.onError("Ошибка подключения");
}
}
class LoginWebChromeClient extends WebChromeClient
{
private LoginCallback callback;
private GameProvider provider;
public LoginWebChromeClient(LoginCallback callback, GameProvider provider)
{
this.callback = callback;
this.provider = provider;
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result)
{
setWebChromeClient(new WebChromeClient());
if (provider.loginIsSuccess(Jsoup.parse(message)))
{
callback.onSuccess();
}
else
{
callback.onError("Неверный логин илм пароль");
}
return true;
}
}
}
In my case, WebView
is needed for hidden authorization (something like headless), since there are not enough parser capabilities. If there was no need for JS, then WebView
could be thrown out, but unfortunately Android doesn't provide anything, even like that, in return.
On the one hand, yes, WebView
is a View (logical, right?). But View-display, that is, what we see. In my case, it is hidden from the user and is used simply because there are no other tools. So I put it in the data layer. This is a platform-specific repository.
But, unfortunately, it is not possible to create WebView
programmatically (and if it is possible, then the context does not belong in the data layer). This means that the option of inheritance is no longer available. If you replace inheritance with composition and pass WebView
through some setter, then this means that the View (it's in the markup!) must go through all the layers.
Both options break the MVP pattern, but are inherently logical. However, what should I do?
2 answers
WebView is a view that is placed on the Activity and in theory, this Activity is the view in the mvp pattern
It turned out something like this:
class HeadLessLoginHelper(private val loginData: LoginData,
private val provider: GameProvider,
private val loginCallback: LoginCallback) : WebViewClient() {
private var loginScriptInjected = false
override fun onPageFinished(webView: WebView, url: String?) {
super.onPageFinished(webView, url)
if (!loginScriptInjected) {
loginScriptInjected = true
webView.runJavascript(readAsset(provider.loginScriptPath), loginData.login, loginData.password)
} else {
webView.webViewClient = WebViewClient()
webView.webChromeClient = JsAlertInterceptor()
webView.runJavascript("function() { alert(document.documentElement.outerHTML); }")
}
}
@Suppress("OverridingDeprecatedMember")
override fun shouldOverrideUrlLoading(webView: WebView, url: String): Boolean {
webView.loadUrl(url)
return true
}
@TargetApi(Build.VERSION_CODES.N)
override fun shouldOverrideUrlLoading(webView: WebView, request: WebResourceRequest): Boolean {
webView.loadUrl(request.url.toString())
return true
}
override fun onReceivedError(webView: WebView, request: WebResourceRequest?,
error: WebResourceError?) {
webView.webViewClient = WebViewClient()
loginCallback.onError(ConnectException())
}
inner class JsAlertInterceptor : WebChromeClient() {
override fun onJsAlert(webView: WebView, url: String,
message: String, result: JsResult?): Boolean {
webView.webChromeClient = WebChromeClient()
val host = Uri.parse(url).host
val afterHost = url.split(host)[1]
try {
provider.checkLoginResult(host, afterHost, Jsoup.parse(message))
loginCallback.onSuccess()
} catch (ex: Exception) {
loginCallback.onError(ex)
}
return false
}
}
companion object {
fun login(webView: WebView, loginData: LoginData,
gameProvider: GameProvider, onSuccess: () -> Unit, onError: (Exception) -> Unit) {
val loginCallback = LoginCallback(onSuccess, onError)
webView.webViewClient = HeadLessLoginHelper(loginData, gameProvider, loginCallback)
webView.loadUrl(gameProvider.loginUrl)
}
}
}