Created
April 23, 2020 09:25
-
-
Save liamkernighan/927a67cdf75516ba7c5c10245601938b to your computer and use it in GitHub Desktop.
Android + WebView communication example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
declare module 'AndroidCommunicator' { | |
global { | |
interface Window { | |
Android: AndroidCommunicator | undefined; | |
} | |
type AndroidCommunicator = { | |
setCallbackExists: (v: boolean) => void; | |
reload: () => void; | |
goTo: (url: string) => void; | |
currentVersion: () => string; | |
checkUpdate: (version: string) => void; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@observer | |
export class RootSSRComponent extends React.Component<RootSSRComponentProps> { | |
constructor(props: RootSSRComponentProps) { | |
super(props); | |
GlobalContext.initialize(props); | |
dayjs.locale('ru'); | |
if(Helpers.isClient()) { | |
if(!GlobalContext.instance.store.loadCurrentUser()) { | |
GlobalContext.instance.store.setRouteOnClient('/auth', ''); | |
} | |
GlobalContext.instance.store.updateUser(); | |
setInterval(GlobalContext.instance.store.updateUser, 5 * 60 * 1000); | |
if(window.Android && window.Android.checkUpdate) { | |
window.Android.checkUpdate(RootSSRComponentStore.currentAppVersion); | |
} | |
} | |
} | |
componentDidMount() { | |
const store = GlobalContext.instance.store; | |
(window as any).globalStore = store; | |
store.clearCallback(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.csm.miasmobile | |
import android.Manifest | |
import android.annotation.SuppressLint | |
import androidx.appcompat.app.AppCompatActivity | |
import android.os.Bundle | |
import android.util.Log | |
import android.view.Window | |
import android.view.View | |
import android.view.WindowManager | |
import androidx.core.app.ActivityCompat | |
import kotlinx.android.synthetic.main.activity_main.* | |
import android.content.Intent | |
import android.net.Uri | |
import android.provider.MediaStore | |
import android.webkit.* | |
import java.io.File | |
import java.io.File.separator | |
import android.app.DownloadManager | |
import android.content.IntentFilter | |
import android.content.BroadcastReceiver | |
import android.content.Context | |
import android.content.pm.PackageManager | |
import android.os.Environment | |
import androidx.core.content.ContextCompat | |
import androidx.core.content.FileProvider | |
class MainActivity : AppCompatActivity() { | |
companion object { | |
private const val FILECHOOSER_RESULTCODE = 1 | |
} | |
private lateinit var _webView: WebView | |
private lateinit var _webViewCommunicator: WebViewCommunicator | |
private var _filePathCallback: ValueCallback<Array<Uri>>? = null | |
private var _capturedImageURI: Uri? = null | |
private var failed: Boolean = false | |
private val baseUrl = BuildConfig.server | |
fun reload() { | |
failed = false; | |
this._webView.post { | |
this._webView.reload() | |
} | |
} | |
fun goTo (url: String) { | |
failed = false; | |
this._webView.post { | |
this._webView.loadUrl(this.baseUrl + url) | |
} | |
} | |
override fun onRequestPermissionsResult(requestCode: Int, | |
permissions: Array<String>, grantResults: IntArray) { | |
if(grantResults.any { it == PackageManager.PERMISSION_DENIED }) { | |
requestPermissions() | |
} | |
} | |
fun requestPermissions() { | |
ActivityCompat.requestPermissions( | |
this, | |
arrayOf( | |
Manifest.permission.ACCESS_COARSE_LOCATION, | |
Manifest.permission.ACCESS_FINE_LOCATION, | |
Manifest.permission.WRITE_EXTERNAL_STORAGE), | |
0) | |
} | |
@SuppressLint("SetJavaScriptEnabled") | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
enableFullScreen() | |
setContentView(R.layout.activity_main) | |
WebView.setWebContentsDebuggingEnabled(true) | |
webView.settings.javaScriptEnabled = true | |
webView.settings.domStorageEnabled = true | |
webView.visibility = View.GONE | |
reloadPrompt.visibility = View.GONE | |
Log.d("ImageId", BuildConfig.logo); | |
val imageId = resources.getIdentifier(BuildConfig.logo, "drawable", packageName) | |
Log.d("ImageId", imageId.toString()); | |
imageView.setImageResource(imageId) | |
requestPermissions() | |
val chromeClient = object : WebChromeClient() { | |
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { | |
Log.d( | |
"MEOW", | |
"${consoleMessage?.message()} \n" + | |
"-- FROM LINE --> ${consoleMessage?.lineNumber()} of ${consoleMessage?.sourceId()}" | |
) | |
return super.onConsoleMessage(consoleMessage) | |
} | |
override fun onShowFileChooser( | |
webView: WebView, | |
filePathCallback: ValueCallback<Array<Uri>>, | |
fileChooserParams: FileChooserParams | |
): Boolean { | |
_filePathCallback = filePathCallback | |
val intent = Intent(Intent.ACTION_GET_CONTENT) | |
intent.type = "image/*" | |
intent.addCategory(Intent.CATEGORY_OPENABLE) | |
// Create camera captured image file path and name | |
val imageStorageDir = File( | |
externalCacheDir, | |
"MIAS" | |
) | |
if (!imageStorageDir.exists()) { | |
// Create AndroidExampleFolder at sdcard | |
imageStorageDir.mkdirs() | |
} | |
val file = File( | |
imageStorageDir.path + separator + "IMG_" | |
+ System.currentTimeMillis().toString() | |
+ ".jpg" | |
) | |
Log.d("File", "File: $file") | |
_capturedImageURI = Uri.fromFile(file) | |
val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) | |
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, _capturedImageURI) | |
val chooser = Intent.createChooser(intent, "Выбор изображения") | |
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(cameraIntent)) | |
startActivityForResult(chooser, FILECHOOSER_RESULTCODE) | |
return true | |
} | |
override fun onGeolocationPermissionsShowPrompt( | |
origin: String?, | |
callback: GeolocationPermissions.Callback? | |
) { | |
callback?.invoke(origin, true, false) | |
} | |
override fun onProgressChanged(view: WebView?, newProgress: Int) { | |
super.onProgressChanged(view, newProgress) | |
if(newProgress == 100 &&! failed) { | |
progressBar.visibility = View.GONE | |
webView.visibility = View.VISIBLE | |
} | |
} | |
} | |
val viewClient = object: WebViewClient() { | |
override fun onReceivedError( | |
view: WebView?, | |
request: WebResourceRequest?, | |
error: WebResourceError? | |
) { | |
super.onReceivedError(view, request, error) | |
if(!(request?.url.toString().contains("api"))) { | |
failed = true; | |
reloadPrompt.visibility = View.VISIBLE | |
webView.visibility = View.GONE | |
progressBar.visibility = View.GONE | |
} | |
} | |
override fun shouldOverrideUrlLoading( | |
view: WebView?, | |
request: WebResourceRequest? | |
): Boolean { | |
if (request?.url.toString().contains(baseUrl) ){ | |
return false; | |
} | |
val intent = Intent(Intent.ACTION_VIEW, request?.url) | |
startActivity(intent); | |
return true; | |
} | |
} | |
webView.webChromeClient = chromeClient | |
webView.webViewClient = viewClient | |
webView.loadUrl(this.baseUrl) // todo загрузить из файла настроек | |
_webViewCommunicator = WebViewCommunicator(this) | |
webView.addJavascriptInterface(_webViewCommunicator, "Android") | |
_webView = webView | |
swipe.setOnRefreshListener { | |
reload() | |
swipe.isRefreshing = false | |
} | |
} | |
private fun enableFullScreen() { | |
requestWindowFeature(Window.FEATURE_NO_TITLE) | |
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) | |
supportActionBar?.hide() | |
} | |
override fun onBackPressed() { | |
when { | |
_webViewCommunicator.callbackIsActive -> webView.evaluateJavascript( | |
"window.globalStore.executeCallback()", | |
null | |
) | |
_webView.canGoBack() -> webView.goBack() | |
else -> super.onBackPressed() | |
} | |
} | |
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { | |
super.onActivityResult(requestCode, resultCode, data) | |
if (requestCode == FILECHOOSER_RESULTCODE) { | |
val value = if (data == null) arrayOf(_capturedImageURI!!) | |
else WebChromeClient.FileChooserParams.parseResult(resultCode, data) | |
_filePathCallback?.onReceiveValue(value) | |
_filePathCallback = null | |
} | |
} | |
fun clickReload(view: View) { | |
progressBar.visibility = View.VISIBLE | |
reloadPrompt.visibility = View.GONE | |
reload(); | |
} | |
fun update(version: String) { | |
if(WebViewCommunicator.version.compareTo(version) == 0) { | |
return; | |
} | |
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) | |
!= PackageManager.PERMISSION_GRANTED) { | |
return | |
} | |
val destination = | |
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) | |
val fileName = "MIAS.apk" | |
val filePath = File(destination, fileName) | |
val uri = Uri.parse("file://$filePath") | |
if (filePath.exists()) { | |
filePath.delete() | |
} | |
val request = DownloadManager.Request(Uri.parse("$baseUrl/app/MIAS.$version.apk")) | |
request.setDestinationUri(uri) | |
val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager | |
manager.enqueue(request) | |
val onComplete = object : BroadcastReceiver() { | |
override fun onReceive(ctxt: Context, intent: Intent) { | |
val install = Intent(Intent.ACTION_VIEW) | |
install.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION | |
val fileProviderUri = FileProvider.getUriForFile( | |
ctxt, | |
BuildConfig.APPLICATION_ID + ".provider", | |
filePath | |
) | |
install.setDataAndType( | |
fileProviderUri, | |
"application/vnd.android.package-archive" | |
) | |
startActivity(install) | |
unregisterReceiver(this) | |
finish() | |
} | |
} | |
registerReceiver(onComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.csm.miasmobile | |
import android.webkit.JavascriptInterface | |
class WebViewCommunicator constructor(val activity: MainActivity) { | |
var callbackIsActive: Boolean = false | |
private set | |
@Suppress("unused") | |
@JavascriptInterface | |
fun setCallbackExists(value: Boolean) { | |
callbackIsActive = value | |
} | |
@Suppress("unused") | |
@JavascriptInterface | |
fun reload() { | |
this.activity.reload(); | |
} | |
@Suppress("unused") | |
@JavascriptInterface | |
fun goTo(url: String) { | |
this.activity.goTo(url); | |
} | |
@Suppress("unused") | |
@JavascriptInterface | |
fun checkUpdate(actualVersion: String) { | |
this.activity.update(actualVersion); | |
} | |
@JavascriptInterface | |
fun currentVersion () = version | |
companion object { | |
val version = "0.1.6" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment