Installation
Android build versions
VykWebView SDK is built with using the following Gradle configuration values.
Name | Value |
---|---|
minSdkVersion | 24 |
targetSdkVersion | 30 |
Maven
The SDK is provided as a Maven distribution from Vyking's Maven repository.
Name | Value |
---|---|
Repository Address | https://vyking-maven-repository.s3.us-east-2.amazonaws.com/releases |
Distribution Name | io.vyking.vykingtracker:vykingtracker-vykwebview-android |
Example Gradle repositories
allprojects {
repositories {
google()
jcenter()
maven {
url = "https://vyking-maven-repository.s3.us-east-2.amazonaws.com/releases"
}
}
}
Example Gradle dependencies:
dependencies {
:
:
:
implementation 'io.vyking.vykingtracker:vykingtracker-vykwebview-android:1.1.4'
}
Releases
- 1.4 First documented release
The Minimum vyking-apparel version required is v1.2.9.
VykWebView Class
The VykWebView class is a subclass of the Android native WebView class and knowledge of this class and how to use it is assumed. An instance of this class must be used to host the vyking-apparel VTO webpage if the vyking-apparel element wants to get its camera feed from the hosting app.
Notes
- A valid VykingData.lzma configuration file, provided by Vyking, is required.
- Camera permission must be granted.
- WebView property settings.javaScriptEnabled must be set to true.
- WebView property settings.mediaPlaybackRequiresUserGesture must be set to false.
Properties
webSocketPort
webSocketPort: Int
A read only property specifying the webSocketPort being used (zero, if not active connection).
setupDelegate
interface VykWebViewSetupInterface {
fun trackerReady(good: Boolean)
fun listenerSetupError(error: Exception?, port: Int)
fun listenerSetupComplete(port: Int)
}
setupDelegate: VykWebViewSetupInterface?
Set this property to define the functions to be called during the VykWebView setup phase.
Methods
setupVykingTracker
data class VykingAttributeSet(
val pathToLzmFile: String,
val useCPUForTensorflow: Boolean?
)
fun setupVykingTracker(vykingAttrs: VykingAttributeSet): Unit
Call this method after setting the setupDelegate and before loading the VTO page into the WebView. It requires the filepath to the ".lzma" file supplied by Vyking.
this.setupVykingTracker(VykingAttributeSet(pathToLzmaFile, null))
this.loadUrl(VTOPageUrl)
Usage
VykWebView uses a webSocket to communicate with the hosting application. The vyking-apparel element needs to be told the port number the hosting application is listening on, but only once the page has finished loading. This is achieved through the use of the delegates: The WebView's webViewClient delegate for page load completion and the VykWebView's setupDelegate for socket setup completion.
Important: Only one instance of the VykingWebView class must exist at any one time and it must be destroyed before it is instanced again. This is easily achieved with the use of an Activity to create and manage the VykWebView instance. However, because an Activity can be re-instanced before the previous instance has been completely destroyed it may attempt to create a new VykWebView before the previous VykWebView has been destroyed. This can be prevented with the use of a static weak reference to the VykWebView hosting activity that the launching activity uses to ensure the activity has been destroyed before launching a new one.
VykWebView Hosting Activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityInstanceRef = WeakReference(this)
}
companion object {
var activityInstanceRef: WeakReference<Activity>? = null
}
Launching Activity
override fun onResume() {
Log.d(tag("onResume"), "()")
super.onResume()
ForkJoinPool.commonPool().submit {
try {
do {
Thread.sleep(100)
} while (VykWebViewActivity.activityInstanceRef?.get().let { if(it == null) false else !it.isDestroyed })
val intent = Intent(this, VykWebViewActivity::class.java)
startActivity(intent)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
WebView.webViewClient Delegate
Setting the WebView's webViewClient delegate allows the onPageFinished function to be used to detect the completion of the webpage load and call an appropriate VykWebView setup method.
Also, VykWebView uses a secure webSocket to communicate with the hosting application, so requires a certificate. The certificate used is a self-signed certificate, so certain conditions need to be detected and handled appropriately. Setting the WebView's webViewClient delegate allows the setting of the onReceivedSslError delegate function which can be used to detect these certificate warnings and handle them appropriately. Note that development web servers also tend to use self-signed certificates, so the delegate function can also be coded to allow these certificates to be granted permission as well.
Example webViewClient delegate
this.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
Log.d(tag("createVykWebView"), "onPageFinished")
(view as VykWebView?)?.setup()
}
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler,
error: SslError
) {
Log.e(tag("onReceivedSslError"), " ")
val serverCertificate = error.certificate
val certDomain = serverCertificate.issuedTo.cName
Log.e(tag("onReceivedSslError"), "certDomain $certDomain, error $error")
when (error.primaryError) {
SslError.SSL_UNTRUSTED -> {
Log.e(
"onReceivedSslError",
"SslError : The certificate authority is not trusted."
)
// Special handling is required for the self-signed certificate used by VykWebView.
// Typically, development web servers also use self-signed certificates so we need to handle these in a special way too.
when (URL(error.url.replace("wss://", "https://")).host) {
"localhost" -> handler.proceed()
"127.0.0.1" -> handler.proceed()
"192.168.0.20" -> handler.proceed()
else -> super.onReceivedSslError(view, handler, error)
}
}
else -> super.onReceivedSslError(view, handler, error)
}
}
}
VykWebView.setupDelegate
The VykWebView setup delegate allows VykWebView to report the success/failure of its various setup requirements. The listenerSetupComplete delegate function reports the listener's port number when VykWebView has successfully started listening for socket connections and can now be used to setup the vyking-apparel HTML element by calling an appropriate VykWebView setup method.
Example VykWebViewSetupDelegate
private inner class VykWebViewSetupDelegate : VykWebViewSetupInterface {
override fun trackerReady(good: Boolean) {
Log.d(tag("trackerReady"), "good: $good")
}
override fun listenerSetupComplete(port: Int) {
Log.d(tag("listenerSetupComplete"), "port $port")
vykWebView?.setup()
}
override fun listenerSetupError(error: Exception?, port: Int) {
Log.d(tag("listenerSetupError"), "port $port, error $error")
}
}
Activity Methods to override
It's recommended to pause/play the the VTO experience when the Android app transitions between paused and resumed states so that expensive camera image capturing and processing is not performed whist the app is in the background. Also, the VykWebView.finish method MUST to be called when the activity is destroyed.
Example overridden activity methods
override fun onDestroy() {
Log.d(tag("onDestroy"), " ")
super.onDestroy()
vykWebView?.finish()
}
override fun onResume() {
Log.d(tag("onResume"), " ")
super.onResume()
vykWebView?.play()
}
override fun onPause() {
Log.d(tag("onPause"), " ")
super.onPause()
vykWebView?.pause()
}
Custom VykWebView Extension
The code examples above refer to example VykWebView methods, such as play and pause, that need to be tailored to the specific implementation of the VTO page being hosted by the VykWebView instance. These methods can be provided through the use of a VykWebView Extension methods. The example extensions below show how a simple vyking-apparel HTML page can be setup, paused, played and have the virtual apparel changed.
/**
There are two stages of initialization that need to complete before we can successfully setup the vto experience. This
setup method can be called on the completion of each stage and only the last one will succeed. The stages are:
1. Finish loading the page
2. Finish setting up the webSocket listener.
*/
fun VykWebView.setup() {
Log.d(tag("setup"), "$webSocketPort")
// Only attempt the setup when we have a valid port number.
webSocketPort.let { port ->
val vykingSrc =
"https://sneaker-window.vyking.io/vyking-assets/customer/vyking-io/yeezy_boost_700_carbon_blue"
val name = "Yeezy Boost"
post(Runnable {
evaluateJavascript(
"""
if (document.querySelector('vyking-apparel') != null) {
document.querySelector('vyking-apparel').setAttribute('alt', '$name');
document.querySelector('vyking-apparel').setAttribute('poster', '$vykingSrc/shoeIcon.png');
document.querySelector('vyking-apparel').setAttribute('apparel', '$vykingSrc/offsets.json');
document.querySelector('vyking-apparel').autocameraInAppSrc = 'wss://localhost:$port';
}
""".trimIndent(),
null
)
})
}
}
fun VykWebView.replaceApparel(url: String, name: String, poster: String) {
Log.d(tag("replaceApparel"), "$url")
post(Runnable {
evaluateJavascript(
"""
document.querySelector('vyking-apparel')?.setAttribute('alt', '$name');
document.querySelector('vyking-apparel')?.setAttribute('poster', '$poster');
document.querySelector('vyking-apparel')?.setAttribute('apparel', '$url');
""".trimIndent(),
null
)
})
}
fun VykWebView.play() {
Log.d(tag("play"), "")
post(Runnable {
evaluateJavascript(
"""
document.querySelector('vyking-apparel')?.play()
""".trimIndent(),
null
)
})
}
fun VykWebView.pause() {
Log.d(tag("pause"), "")
post(Runnable {
evaluateJavascript(
"""
document.querySelector('vyking-apparel')?.pause()
""".trimIndent(),
null
)
})
}