Skip to content

Instantly share code, notes, and snippets.

@liamkernighan
Created April 23, 2020 09:25
Show Gist options
  • Save liamkernighan/927a67cdf75516ba7c5c10245601938b to your computer and use it in GitHub Desktop.
Save liamkernighan/927a67cdf75516ba7c5c10245601938b to your computer and use it in GitHub Desktop.
Android + WebView communication example
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;
}
}
}
@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();
}
}
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))
}
}
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