59. Scoped Storage accessing files
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Api 29+
android Q and higher
API 28-
android Pie and earlier
60. Scoped Storage
accessing app files
readwrite
MyApp Accessing App Storage
Photos MediaStore.Images
Videos MediaStore.Video
Audio MediaStore.Audio
* No permission needed!
Other files No access
63. Scoped Storage supporting legacy storage
<manifest>
<application
android:requestLegacyExternalStorage="true">
...
</application>
</manifest>
* Android R will only work with scoped storage
70. Pre - Q
<manifest>
<!-- ACCESS_COARSE_LOCATION - city level -->
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- ACCESS_FINE_LOCATION - as best we can do -->
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"/>
...
</manifest>
71. Targets Q
<manifest>
<!-- ACCESS_COARSE_LOCATION - city level -->
<uses-permission
android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- ACCESS_FINE_LOCATION - as best we can do -->
<uses-permission
android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission
android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
...
</manifest>
75. //Target Q only, request "while-in-use" first
ActivityCompat.requestPermissions(this, arrayOf(
android.Manifest.permission.ACCESS_COARSE_LOCATION // or FINE
), REQUEST_CODE)
76. //Target Q only, request "while-in-use" first
ActivityCompat.requestPermissions(this, arrayOf(
android.Manifest.permission.ACCESS_COARSE_LOCATION // or FINE
), REQUEST_CODE)
//Request "all-the-time" (only if needed)
ActivityCompat.requestPermissions(this, arrayOf(
android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
), REQUEST_CODE)
81. Foreground service requires location type
<service
android:name="MyNavigationService"
android:foregroundServiceType="location" ... >
...
</service>
82. If you didn’t declare type location
● Has permission to location ‘while-in-use’ -
The service has NO permission for location
<service
android:name="MyNavigationService" ... >
</service>
83. If you didn’t declare type location
● Has permission to location ‘while-in-use’ -
The service has NO permission for location
● Has permission to location ‘all-the-time’ -
The service has permission for location
<service
android:name="MyNavigationService" ... >
</service>
115. ModesDayNight
MODE_NIGHT_YES
//Always use the dark theme
MODE_NIGHT_NO
//Always use the light theme DEFAULT PRE-Q
MODE_NIGHT_FOLLOW_SYSTEM
//Follows the current system settings DEFAULT ON Q
116. ModesDayNight
MODE_NIGHT_YES
//Always use the dark theme
MODE_NIGHT_NO
//Always use the light theme DEFAULT PRE-Q
MODE_NIGHT_FOLLOW_SYSTEM
//Follows the current system settings DEFAULT ON Q
MODE_NIGHT_AUTO_BATTERY
//Dark when battery saver is enabled API 21+ AND AppCompat-v1.1.0
118. Support Night ModeDayNight
● AppCompatDelegate.setDefaultNightMode(mode : Int)
● getDelegate().setLocalNightMode(mode : Int)
119. Support Night ModeDayNight
● AppCompatDelegate.setDefaultNightMode(mode : Int)
● getDelegate().setLocalNightMode(mode : Int)
Note: the values are not persisted
133. Bubbles
● They float on top of other app
content and follow the user
● Can be expanded to reveal app
functionality and information
134. Bubbles
● They float on top of other app
content and follow the user
● Can be expanded to reveal app
functionality and information
● Built into the notification system
135. Build a Bubble
● Configure the activity shown in the bubble
● Construct BubbleMetadata
● Add the metadata to your notification and send
136. Configure the activity shown in the bubble
<!--Must be embeddable,documentLaunchMode always,and resizable-->
<activity
android:name=".bubbles.BubbleActivity"
...
/>
137. Configure the activity shown in the bubble
<!--Must be embeddable,documentLaunchMode always,and resizable-->
<activity
android:name=".bubbles.BubbleActivity"
android:allowEmbedded="true"
...
/>
138. Configure the activity shown in the bubble
<!--Must be embeddable,documentLaunchMode always,and resizable-->
<activity
android:name=".bubbles.BubbleActivity"
android:allowEmbedded="true"
android:documentLaunchMode="always"
...
/>
139. Configure the activity shown in the bubble
<!--Must be embeddable,documentLaunchMode always,and resizable-->
<activity
android:name=".bubbles.BubbleActivity"
android:allowEmbedded="true"
android:documentLaunchMode="always"
android:resizeableActivity="true"
...
/>
140. Construct BubbleMetadata
val intent = Intent(mContext, MainActivity::class.java)
val bubbleIntent = PendingIntent.getActivity(
mContext, 0 /* requestCode */, intent, 0/* flags*/)
141. Construct BubbleMetadata
val intent = Intent(mContext, MainActivity::class.java)
val bubbleIntent = PendingIntent.getActivity(
mContext, 0 /* requestCode */, intent, 0/* flags*/)
//Metadata to go on the notification
val metadata = Notification.BubbleMetadata.Builder()
142. Construct BubbleMetadata
val intent = Intent(mContext, MainActivity::class.java)
val bubbleIntent = PendingIntent.getActivity(
mContext, 0 /* requestCode */, intent, 0/* flags*/)
//Metadata to go on the notification
val metadata = Notification.BubbleMetadata.Builder()
//Icon is not displayed in Beta Q
.setIcon(Icon.createWithResource(mContext,
R.drawable.ic_bubble))
143. Construct BubbleMetadata
val intent = Intent(mContext, MainActivity::class.java)
val bubbleIntent = PendingIntent.getActivity(
mContext, 0 /* requestCode */, intent, 0/* flags*/)
//Metadata to go on the notification
val metadata = Notification.BubbleMetadata.Builder()
//Icon is not displayed in Beta Q
.setIcon(Icon.createWithResource(mContext,
R.drawable.ic_bubble))
.setIntent(bubbleIntent).build()
144. Add the BubbleMetadata to your
notification and send
var builder = Notification.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
...
.setBubbleMetadata(metadata)
145. Add the BubbleMetadata to your
notification and send
var builder = Notification.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
...
.setBubbleMetadata(metadata)
//Send the notification
mNotificationManager.notify(BUBBLE_ID, builder.build())
146. When to use Bubbles?
● Messaging
● Calling
● User initiated
150. Sharing
● Content preview - supports
images and text
● New sharing shortcut API -
Doesn’t need to start your app
151. Sharing
● Content preview - supports
images and text
● New sharing shortcut API -
Doesn’t need to start your app
● Copy to clipboard up top
152. Sharing
● Content preview - supports
images and text
● New sharing shortcut API -
Doesn’t need to start your app
● Copy to clipboard up top
● It’s really really fast
155. Providing a sharing shortcut
val sic = mutableListOf<ShortcutInfoCompat>()
sic.add( ShortcutInfoCompat.Builder(context, personId)
156. Providing a sharing shortcut
val sic = mutableListOf<ShortcutInfoCompat>()
sic.add( ShortcutInfoCompat.Builder(context, personId)
.setShortLabel("Chet")
157. Providing a sharing shortcut
val sic = mutableListOf<ShortcutInfoCompat>()
sic.add( ShortcutInfoCompat.Builder(context, personId)
.setShortLabel("Chet")
.setLongLabel("Chet Haase")
158. Providing a sharing shortcut
val sic = mutableListOf<ShortcutInfoCompat>()
sic.add( ShortcutInfoCompat.Builder(context, personId)
.setShortLabel("Chet")
.setLongLabel("Chet Haase")
.setPerson(...)//Person can improve ranking
159. Providing a sharing shortcut
val sic = mutableListOf<ShortcutInfoCompat>()
sic.add( ShortcutInfoCompat.Builder(context, personId)
.setShortLabel("Chet")
.setLongLabel("Chet Haase")
.setPerson(...)//Person can improve ranking
.setIcon(/* Icon */)
160. Providing a sharing shortcut
val sic = mutableListOf<ShortcutInfoCompat>()
sic.add( ShortcutInfoCompat.Builder(context, personId)
.setShortLabel("Chet")
.setLongLabel("Chet Haase")
.setPerson(...)//Person can improve ranking
.setIcon(/* Icon */)
.setCategories(category)//required to be a Sharing Shortcut
161. Providing a sharing shortcut
val sic = mutableListOf<ShortcutInfoCompat>()
sic.add( ShortcutInfoCompat.Builder(context, personId)
.setShortLabel("Chet")
.setLongLabel("Chet Haase")
.setPerson(...)//Person can improve ranking
.setIcon(/* Icon */)
.setCategories(category)//required to be a Sharing Shortcut
.setIntent(...).build())
162. Providing a sharing shortcut
val sic = mutableListOf<ShortcutInfoCompat>()
sic.add( ShortcutInfoCompat.Builder(context, personId)
.setShortLabel("Chet")
.setLongLabel("Chet Haase")
.setPerson(...)//Person can improve ranking
.setIcon(/* Icon */)
.setCategories(category)//required to be a Sharing Shortcut
.setIntent(...).build())
ShortcutManagerCompat.addDynamicShortcuts(context, sic)
164. Adding a rich preview content
var share = Intent(ACTION_SEND)
...
165. Adding a rich preview content
var share = Intent(ACTION_SEND)
...
//Metadata must be in intent before Intent#crateChooser
share.putExtra(EXTRA_TITLE,"Introducing Android Q Beta")
166. Adding a rich preview content
var share = Intent(ACTION_SEND)
...
//Metadata must be in intent before Intent#crateChooser
share.putExtra(EXTRA_TITLE,"Introducing Android Q Beta")
share.clipData = ClipData.newUri(contentResolver,
"thumbnail", myContentProviderUri)
share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
167. Adding a rich preview content
var share = Intent(ACTION_SEND)
...
//Metadata must be in intent before Intent#crateChooser
share.putExtra(EXTRA_TITLE,"Introducing Android Q Beta")
share.clipData = ClipData.newUri(contentResolver,
"thumbnail", myContentProviderUri)
share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
share = Intent.createChooser(share,null /* no longer used */)
189. Benchmark
● Makes it easy to benchmark your code
● Handles warmup and clock stability for you
● Gives you a accurate result
● Runs as instrumented test
190. What should I benchmark
● RecyclerView scrolling
● Data processing
● Pieces of code that get used repeatedly
206. Jetpack compose
UI is defined as a function
● Take data as input
● Emits UI hierarchy when invoked
@Composable
fun Greeting(name: String) {
Text("Hello: $name")
}
208. What is compose?
● A set of jetpack UI widgets
● A Kotlin compiler plugin
● Unbundled from platform release
● Uses canvas instead of Views
● Fully compatible with your existing app/code
231. Easy….
private val captureCallback = object : CameraCaptureSession.CaptureCallback() {
private fun process(result: CaptureResult) {
when (state) {
STATE_PREVIEW -> Unit // Do nothing when the camera preview is working normally.
STATE_WAITING_LOCK -> capturePicture(result)
STATE_WAITING_PRECAPTURE -> {
// CONTROL_AE_STATE can be null on some devices
val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
if (aeState == null ||
aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
state = STATE_WAITING_NON_PRECAPTURE
}
}
STATE_WAITING_NON_PRECAPTURE -> {
// CONTROL_AE_STATE can be null on some devices
val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
state = STATE_PICTURE_TAKEN
captureStillPicture()
}
}
}
}
private fun capturePicture(result: CaptureResult) {
val afState = result.get(CaptureResult.CONTROL_AF_STATE)
if (afState == null) {
captureStillPicture()
} else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
|| afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
// CONTROL_AE_STATE can be null on some devices
val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
state = STATE_PICTURE_TAKEN
captureStillPicture()
} else {
runPrecaptureSequence()
}
}
}
override fun onCaptureProgressed(session: CameraCaptureSession,
request: CaptureRequest,
partialResult: CaptureResult) {
process(partialResult)
}
override fun onCaptureCompleted(session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult) {
process(result)
}
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_camera2_basic, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
view.findViewById<View>(R.id.picture).setOnClickListener(this)
view.findViewById<View>(R.id.info).setOnClickListener(this)
textureView = view.findViewById(R.id.texture)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
file = File(activity.getExternalFilesDir(null), PIC_FILE_NAME)
}
nsor coordinate
* @param maxWidth The maximum width that can be chosen
* @param maxHeight The maximum height that can be chosen
* @param aspectRatio The aspect ratio
* @return The optimal `Size`, or an arbitrary one if none were big enough
*/
@JvmStatic private fun chooseOptimalSize(
choices: Array<Size>,
textureViewWidth: Int,
textureViewHeight: Int,
maxWidth: Int,
maxHeight: Int,
aspectRatio: Size
): Size {
// Collect the supported resolutions that are at least as big as the preview Surface
val bigEnough = ArrayList<Size>()
// Collect the supported resolutions that are smaller than the preview Surface
val notBigEnough = ArrayList<Size>()
val w = aspectRatio.width
val h = aspectRatio.height
for (option in choices) {
if (option.width <= maxWidth && option.height <= maxHeight &&
option.height == option.width * h / w) {
if (option.width >= textureViewWidth && option.height >= textureViewHeight) {
bigEnough.add(option)
} else {
notBigEnough.add(option)
}
}
}
// Pick the smallest of those big enough. If there is no one big enough, pick the
// largest of those not big enough.
if (bigEnough.size > 0) {
return Collections.min(bigEnough, CompareSizesByArea())
} else if (notBigEnough.size > 0) {
return Collections.max(notBigEnough, CompareSizesByArea())
} else {
Log.e(TAG, "Couldn't find any suitable preview size")
return choices[0]
}
}
@JvmStatic fun newInstance(): Camera2BasicFragment = Camera2BasicFragment()
}
}
override fun onResume() {
super.onResume()
startBackgroundThread()
// When the screen is turned off and turned back on, the SurfaceTexture is already
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
// a camera and start preview from here (otherwise, we wait until the surface is ready in
// the SurfaceTextureListener).
if (textureView.isAvailable) {
openCamera(textureView.width, textureView.height)
} else {
textureView.surfaceTextureListener = surfaceTextureListener
}
}
override fun onPause() {
closeCamera()
stopBackgroundThread()
super.onPause()
}
private fun requestCameraPermission() {
if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
ConfirmationDialog().show(childFragmentManager, FRAGMENT_DIALOG)
} else {
requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
}
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>,
grantResults: IntArray) {
if (requestCode == REQUEST_CAMERA_PERMISSION) {
if (grantResults.size != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
ErrorDialog.newInstance(getString(R.string.request_permission))
.show(childFragmentManager, FRAGMENT_DIALOG)
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
/**
* Sets up member variables related to camera.
*
* @param width The width of available size for camera preview
* @param height The height of available size for camera preview
*/
private fun setUpCameraOutputs(width: Int, height: Int) {
val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
try {
for (cameraId in manager.cameraIdList) {
val characteristics = manager.getCameraCharacteristics(cameraId)
// We don't use a front facing camera in this sample.
val cameraDirection = characteristics.get(CameraCharacteristics.LENS_FACING)
if (cameraDirection != null &&
cameraDirection == CameraCharacteristics.LENS_FACING_FRONT) {
continue
}
val map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue
// For still image captures, we use the largest available size.
val largest = Collections.max(
Arrays.asList(*map.getOutputSizes(ImageFormat.JPEG)),
CompareSizesByArea())
imageReader = ImageReader.newInstance(largest.width, largest.height,
ImageFormat.JPEG, /*maxImages*/ 2).apply {
setOnImageAvailableListener(onImageAvailableListener, backgroundHandler)
}
// Find out if we need to swap dimension to get the preview size relative to sensor
// coordinate.
val displayRotation = activity.windowManager.defaultDisplay.rotation
sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)
val swappedDimensions = areDimensionsSwapped(displayRotation)
val displaySize = Point()
activity.windowManager.defaultDisplay.getSize(displaySize)
val rotatedPreviewWidth = if (swappedDimensions) height else width
val rotatedPreviewHeight = if (swappedDimensions) width else height
var maxPreviewWidth = if (swappedDimensions) displaySize.y else displaySize.x
var maxPreviewHeight = if (swappedDimensions) displaySize.x else displaySize.y
if (maxPreviewWidth > MAX_PREVIEW_WIDTH) maxPreviewWidth = MAX_PREVIEW_WIDTH
if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) maxPreviewHeight = MAX_PREVIEW_HEIGHT
// Danger, W.R.! Attempting to use too large a preview size could exceed the camera
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
// garbage capture data.
previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture::class.java),
rotatedPreviewWidth, rotatedPreviewHeight,
maxPreviewWidth, maxPreviewHeight,
largest)
// We fit the aspect ratio of TextureView to the size of preview we picked.
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
textureView.setAspectRatio(previewSize.width, previewSize.height)
} else {
textureView.setAspectRatio(previewSize.height, previewSize.width)
}
// Check if the flash is supported.
flashSupported =
characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) == true
val captureBuilder = cameraDevice?.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE)?.apply {
addTarget(imageReader?.surface)
// Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
// We have to take that into account and rotate JPEG properly.
// For devices with orientation of 90, we return our mapping from ORIENTATIONS.
// For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
set(CaptureRequest.JPEG_ORIENTATION,
(ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360)
// Use the same AE and AF modes as the preview.
set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
}?.also { setAutoFlash(it) }
val captureCallback = object : CameraCaptureSession.CaptureCallback() {
override fun onCaptureCompleted(session: CameraCaptureSession,
request: CaptureRequest,
result: TotalCaptureResult) {
activity.showToast("Saved: $file")
Log.d(TAG, file.toString())
unlockFocus()
}
}
captureSession?.apply {
stopRepeating()
abortCaptures()
capture(captureBuilder?.build(), captureCallback, null)
}
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
}
}
/**
* Unlock the focus. This method should be called when still image capture sequence is
* finished.
*/
private fun unlockFocus() {
try {
// Reset the auto-focus trigger
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL)
setAutoFlash(previewRequestBuilder)
captureSession?.capture(previewRequestBuilder.build(), captureCallback,
backgroundHandler)
// After this, the camera will go back to the normal state of preview.
state = STATE_PREVIEW
captureSession?.setRepeatingRequest(previewRequest, captureCallback,
backgroundHandler)
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
}
}
override fun onClick(view: View) {
when (view.id) {
R.id.picture -> lockFocus()
R.id.info -> {
if (activity != null) {
AlertDialog.Builder(activity)
.setMessage(R.string.intro_message)
.setPositiveButton(android.R.string.ok, null)
.show()
}
}
}
}
private fun setAutoFlash(requestBuilder: CaptureRequest.Builder) {
if (flashSupported) {
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
}
}
companion object {
/**
* Conversion from screen rotation to JPEG orientation.
*/
private val ORIENTATIONS = SparseIntArray()
private val FRAGMENT_DIALOG = "dialog"
init {
ORIENTATIONS.append(Surface.ROTATION_0, 90)
ORIENTATIONS.append(Surface.ROTATION_90, 0)
ORIENTATIONS.append(Surface.ROTATION_180, 270)
ORIENTATIONS.append(Surface.ROTATION_270, 180)
}
/**
* Tag for the [Log].
*/
private val TAG = "Camera2BasicFragment"
/**
* Camera state: Showing camera preview.
*/
private val STATE_PREVIEW = 0
/**
* Camera state: Waiting for the focus to be locked.
*/
private val STATE_WAITING_LOCK = 1
/**
* Camera state: Waiting for the exposure to be precapture state.
*/
private val STATE_WAITING_PRECAPTURE = 2
/**
* Camera state: Waiting for the exposure state to be something other than precapture.
*/
private val STATE_WAITING_NON_PRECAPTURE = 3
/**
* Camera state: Picture was taken.
*/
private val STATE_PICTURE_TAKEN = 4
/**
* Max preview width that is guaranteed by Camera2 API
*/
private val MAX_PREVIEW_WIDTH = 1920
/**
* Max preview height that is guaranteed by Camera2 API
*/
private val MAX_PREVIEW_HEIGHT = 1080
/**
this.cameraId = cameraId
// We've found a viable camera and finished setting up member variables,
// so we don't need to iterate through other available cameras.
return
}
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
} catch (e: NullPointerException) {
// Currently an NPE is thrown when the Camera2API is used but not supported on the
// device this code runs.
ErrorDialog.newInstance(getString(R.string.camera_error))
.show(childFragmentManager, FRAGMENT_DIALOG)
}
}
/**
* Determines if the dimensions are swapped given the phone's current rotation.
*
* @param displayRotation The current rotation of the display
*
* @return true if the dimensions are swapped, false otherwise.
*/
private fun areDimensionsSwapped(displayRotation: Int): Boolean {
var swappedDimensions = false
when (displayRotation) {
Surface.ROTATION_0, Surface.ROTATION_180 -> {
if (sensorOrientation == 90 || sensorOrientation == 270) {
swappedDimensions = true
}
}
Surface.ROTATION_90, Surface.ROTATION_270 -> {
if (sensorOrientation == 0 || sensorOrientation == 180) {
swappedDimensions = true
}
}
else -> {
Log.e(TAG, "Display rotation is invalid: $displayRotation")
}
}
return swappedDimensions
}
/**
* Opens the camera specified by [Camera2BasicFragment.cameraId].
*/
private fun openCamera(width: Int, height: Int) {
val permission = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA)
if (permission != PackageManager.PERMISSION_GRANTED) {
requestCameraPermission()
return
}
setUpCameraOutputs(width, height)
configureTransform(width, height)
val manager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
try {
// Wait for camera to open - 2.5 seconds is sufficient
if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw RuntimeException("Time out waiting to lock camera opening.")
}
manager.openCamera(cameraId, stateCallback, backgroundHandler)
} catch (e: CameraAccessException) {
Log.e(TAG, e.toString())
} catch (e: InterruptedException) {
throw RuntimeException("Interrupted while trying to lock camera opening.", e)
}
}
/**
* Closes the current [CameraDevice].
*/
private fun closeCamera() {
try {
cameraOpenCloseLock.acquire()
captureSession?.close()
captureSession = null
cameraDevice?.close()
cameraDevice = null
imageReader?.close()
imageReader = null
} catch (e: InterruptedException) {
throw RuntimeException("Interrupted while trying to lock camera closing.", e)
} finally {
cameraOpenCloseLock.release()
}
}
/**
* Starts a background thread and its [Handler].
*/
private fun startBackgroundThread() {
backgroundThread = HandlerThread("CameraBackground").also { it.start() }
backgroundHandler = Handler(backgroundThread?.looper)
}
/**
* Stops the background thread and its [Handler].
hen we get a response in
* [.captureCallback] from both [.lockFocus].
*/
private fun captureStillPicture() {
try {
if (activity == null || cameraDevice == null) return
val rotation = activity.windowManager.defaultDisplay.rotation
// This is the CaptureRequest.Builder that we use to take a picture.
e
238. Preview
val previewConfig = PreviewConfig.Builder().build()
val preview = Preview(previewConfig)
preview.setOnPreviewOutputUpdateListener {
previewOutput: Preview.PreviewOutput? ->
/* Your code here.
* For example, use previewOutput?.getSurfaceTexture()
* and post to a GL renderer.
*/
}
239. Preview
val previewConfig = PreviewConfig.Builder().build()
val preview = Preview(previewConfig)
preview.setOnPreviewOutputUpdateListener {
previewOutput: Preview.PreviewOutput? ->
/* Your code here.
* For example, use previewOutput?.getSurfaceTexture()
* and post to a GL renderer.
*/
}
CameraX.bindToLifecycle(this as LifecycleOwner, preview)
256. fun onClick() {
val file = File(...)
imageCapture.takePicture(
)
}
Save an Image on user action
257. fun onClick() {
val file = File(...)
imageCapture.takePicture(file,
)
}
Save an Image on user action
258. fun onClick() {
val file = File(...)
imageCapture.takePicture(file,
object : ImageCapture.OnImageSavedListener {
override fun onError(error: ImageCapture.UseCaseError,
message: String, exc: Throwable?) {
// insert your code here.
}
override fun onImageSaved(file: File) {
// insert your code here.
}
})
}
Listen to save result
261. Extensions
val builder = ImageCaptureConfig.Builder()
val bokehImageCapture = BokehImageCaptureExtender.create(builder)
262. Extensions
val builder = ImageCaptureConfig.Builder()
val bokehImageCapture = BokehImageCaptureExtender.create(builder)
if (bokehImageCapture.isExtensionAvailable()) {
bokehImageCapture.enableExtension()
}
מידע על מיקום נותן את התכנים השימושיים, המעשירים והמרגשים ביותר למשתמשים - עזרה בניווט ממקום למקום, מציאת מסעדות וחנויות ועוד. .
לפני אנדרואיד קיו הרשאת מיקום היה בעל ערך בינארי -או שכן או שלא. אם יוזר מסכים לתת לאפליקציה הרשאה למיקום, האפליקציה יכולה לעקוב אחר המיקום גם כשהיוזר משתמש באפליקציה וגם כשהוא לא.
באנדרואיד קיו נוספת ליוזר אופציה חדשה - אשר רק כשאני משתמש באפליקציה. אם היוזר בוחר באופציה הזאת - האפליקציה רשאית לגשת למיקום רק כשהאפליקציה בפורגראונד.
אז מתי האפליקצייה שלי נחשבת בפורגראונד
אוקי, אז ישנם שני מקרים : המקרה הראשון הוא כשהאפליקציה פתוחה והיוזר רואה אותה על המסך, פורגראונד אקטיביטי, והשני הוא כשהאפליקציה מריצה פורגראונד סרויס. בגדול פורגראונד סרוויס זה סרוויס רגיל, אבל במידה והאפליקציה סגורה או לא מוצגת במסך נוצרת נוטיפיקציה כך שהיוזר יודע שעדיין מתרחש איזשהו תהליך באפליקציה. דוגמה לשימוש בפורגראונד סרוויס - ניווט באפליקציית גוגל מפס, אם היוזר יוצא מהאפליקציה או סוגר אותה במהלך ניווט, האפליקציה ממשיכה להכווין את המשתמש דרך נוטיפיקציה. אוקיי, אז במהלך הדיבור על הרשאות מיקום כל פעם שאגיד , אפליקצייה בפורגראונד, הכוונה היא או שיש אקטיביטי בפורגראונד או שיש פורגראונד סרוויס, אחרת האפליקציה באקגראונד.
.באנדרואיד קיו, אם האפליקציה שלכם מוכוונת לקיו אס די קיי, צריך להוסיף הצהרה לשימוש בלוקשיין בבאקגראונד. אקסס קורס לוקשיין ופיין לוקשיין יאפשרו לאפליקציה לגשת למיקום רק כשהיא בפורגראונד.
.באנדרואיד קיו, אם האפליקציה שלכם מוכוונת לקיו אס די קיי, צריך להוסיף הצהרה לשימוש בלוקשיין בבאקגראונד. אקסס קורס לוקשיין ופיין לוקשיין יאפשרו לאפליקציה לגשת למיקום רק כשהיא בפורגראונד.
אם האפליקצייה לא מוכוונת לקיו אס די קיי אבל רצה על אנדראויד קיו - המערכת מוסיפה את הבאקגראונד פרמישיין אוטומטית,.
אחד היתרונות לטרגט את אנדרואיד קיו , הוא ההפרדה בין בקשות לפורגראונד ולבאקגראונד. ככה ליוזר יהיה קל יותר להבין למה הוא צריך לאפשר שימוש במיקום והסיכוי שהוא יתן לכם את האישור יגדל. בתמונה ניתן לראות דוגמה לדיאלוג לבקשת שימוש במיקום בבאקגראונד אחרי שהיוזר כבר אישר שימוש בפורגראונד.
אם האפליקצייה לא מוכוונת לקיו אס די קיי אבל רצה על אנדראויד קיו - המערכת מוסיפה את הבאקגראונד פרמישיין אוטומטית,.
.לבקשת הרשאת מיקום בבאקגראונד, מומלץ קודם לבדוק אם קיים אישור לשימוש בפורגראונד ואם יש אז לבקש רק את ההרשאה הנוספת
.לבקשת הרשאת מיקום בבאקגראונד, מומלץ קודם לבדוק אם קיים אישור לשימוש בפורגראונד ואם יש אז לבקש רק את ההרשאה הנוספת
בדיקת הרשאה למיקום בבאקגראונד מאד דומה, פשוט צריך לבדוק אם יש פרמישיין לאקסס באקגראונד לוקשיין
בדיקת הרשאה למיקום בבאקגראונד מאד דומה, פשוט צריך לבדוק אם יש פרמישיין לאקסס באקגראונד לוקשיין
מה קורה אם יוזר הוריד את האפליקצייה על אנדרואיד פיי ואחר כך עדכן לאנדרואיד קיו? אם היוזר נתן הרשאות למיקום אז באופן אוטומטי יהיה לאפליקציה גם הרשאה למיקום בבאקגראונד, כך שלא יהיה שינוי התנהגות באפליקציה.
מה קורה אם יוזר הוריד את האפליקצייה על אנדרואיד פיי ואחר כך עדכן לאנדרואיד קיו? אם היוזר נתן הרשאות למיקום אז באופן אוטומטי יהיה לאפליקציה גם הרשאה למיקום בבאקגראונד, כך שלא יהיה שינוי התנהגות באפליקציה.
מאנדרואיד קיו אפליקציות שצריכות לקבל מיקום מתוך פורגראונד סרוויס צריכות להצהיר שזהו סרוויס מסוג לוקשיין במניפסט.
אם לאפליקצייה יש אישור לשימוש במיקום בבאדגראונד לסרוויס יהיה גישה למיקום. השינויים האלה הם עבור כל האפליקציות ללא התחשבות בטרגט אס די קיי. במילים אחרות - אפליקצייה צריכה להצהיר לוקיישן טייפ אם היא רוצה להשתמש במיקום בפורגאונדסרויס וגם לבקש מהיוזר שימוש במיקום פורגראונד בלבד
אם לאפליקצייה יש אישור לשימוש במיקום בבאדגראונד לסרוויס יהיה גישה למיקום. השינויים האלה הם עבור כל האפליקציות ללא התחשבות בטרגט אס די קיי. במילים אחרות - אפליקצייה צריכה להצהיר לוקיישן טייפ אם היא רוצה להשתמש במיקום בפורגאונדסרויס וגם לבקש מהיוזר שימוש במיקום פורגראונד בלבד
יוזר יקבל באופן חד-פעמי פר-אפליקציה כשהיוזר מעניק לאפליקציה הרשאה לבאקגראטנד יחד עם פיין לוקישיין. עם הוא עושה שינויים בהרשאות הוא יוכל לקבל את הנוטיפיקציה הזאת שוב
כשיוזר לוחץ על הנוטיפיקציה יפתח מסך הרשאות המיקום של האפליקציה שם הוא יוכל לשנות את ההרשאות למיקום רק בפורגראונד או לחסום לגמרי את האפליקציה ממידע על המיקום.
לפני אנדראויד קיו, אם יוזר במצב טיסה פותח אפליקציה שצריכה אינטרנט, המקסימום שהאפליקציה יכלה לעשות זה להציג לו דיאלוג שלחיצה עליו תפתח את הסטינגנס של המכשיר.
היוזר יכול לשנות הגדרות מבלי לעזוב את האפליקציה, להפעיל וויפי ולבחור רשת..
הגדרות שמע
הגדרות שמע
הגדרות שמע
הגדרות שמע
הגדרות שמע
הגדרות שמע
ובעתיד גוגל מתכוונים לעשות ווראפר לכל זה שיפתח את העמוד בסטיגנס המתאים ביותר. ועכשיו נעבור לדבר על דארק ט’ים
דארק ט’ים
אז למה דארק ט’ים חשוב?
אז דבר ראשון תמיכה בשימוש באפליקציה במקומות חשוכים, כמה פעמים יצא לכם להשתמש בפלאפון בחדר חשוך ולהסתנוור מהמסך?
בנוסף, הארת פחות פיקסלים במסך מפחית את צריכת הסוללה משמעותית.למעשה בדיקות בגוגל מצאו כי דארק ט’ים חוסך עד 60% מצריכת הסוללה כשהאפליקציה בפורגראונד.
ואחרון, נגישות, דארק ט’ים פחות מאמץ את העיניים ולהרבה אנשים שיותר נוח
אז יש כמה דרכים להפעיל דארק ט’ים באנדרואיד קיו - האופציה הראשונה היא דרך הדיספליי סטינגס
אופציה שנייה דרך הקוייק סטינגס
והאופציה השלישית דרך מצב בטרי סייבר במכשירי פיקסל .
חשוב להבין כי מעבר לדארק ט’ים זה לא רק מעבר של המערכת ליו איי תואם אלה זה נוגע בכל האפליקציות. יוזרים יצפו שהאפליקציה שלכם תתנהג בהתאם ולכן חשוב לתמוך בדארק ט’ים
דיינייט הוא פיצ’ר באפקומפט שעוזר לנו ליישם בקלות דארקט’ים באפליקציה. הוא יקח רסורסים מהנייט קווליפייר בדארק ט’ם. עובד מאנדרואיד 4.0. ושופר אפקומפט 1.1
דיינייט הוא פיצ’ר באפקומפט שעוזר לנו ליישם בקלות דארקט’ים באפליקציה. הוא יקח רסורסים מהנייט קווליפייר בדארק ט’ם. עובד מאנדרואיד 4.0. ושופר אפקומפט 1.1
בשביל להשתמש בדיינייט הדבר הראשון שצריך לעשות הוא לשנות את הפרנט של הט’ים שלכם לאחד הט’ימים של דיינייט
Google recommend using Material Design Components, since its color theming system (such as the theme attributes ?attr/colorSurface and ?attr/colorOnSurface) provides easy access to suitable colors.
כפי שציינתי קודם אפקומפט מאפשרת ליצור ריסורס קווליפייר של נייט. ככה בדיוק הט’ים מיושם, בלייט ט’ים יש דוט לייט ובנייט ט’ים ייושם הט’ים הכהה..
כפי שציינתי קודם אפקומפט מאפשרת ליצור ריסורס קווליפייר של נייט. ככה בדיוק הט’ים מיושם, בלייט ט’ים יש דוט לייט ובנייט ט’ים ייושם הט’ים הכהה..
כפי שציינתי קודם אפקומפט מאפשרת ליצור ריסורס קווליפייר של נייט. ככה בדיוק הט’ים מיושם, בלייט ט’ים יש דוט לייט ובנייט ט’ים ייושם הט’ים הכהה..
נייט אוטו- באטרי -דארקמוד מופעל בכל פעם שבטרי סייבר פועל מאייפיאי 21
נייט אוטו- באטרי -דארקמוד מופעל בכל פעם שבטרי סייבר פועל מאייפיאי 21
נייט אוטו- באטרי -דארקמוד מופעל בכל פעם שבטרי סייבר פועל מאייפיאי 21
נייט אוטו- באטרי -דארקמוד מופעל בכל פעם שבטרי סייבר פועל מאייפיאי 21
הדבר הבא שצריך לעשות הוא להגיד לאפקומפט באיזה מוד להשתמש. יש לנו שני אייפיי’ס לזה, הראשון סטדיפולט נייט מוד. הוא משמש כשרוצים להגדיר מוד עבור כל האפליקציה, כל האקטיביטיס. אם רוצים להגדיר מוד עבור אקטיביטי מסויים ניתן להשתמש בסט לוקל נייט מוד
הדבר הבא שצריך לעשות הוא להגיד לאפקומפט באיזה מוד להשתמש. יש לנו שני אייפיי’ס לזה, הראשון סטדיפולט נייט מוד. הוא משמש כשרוצים להגדיר מוד עבור כל האפליקציה, כל האקטיביטיס. אם רוצים להגדיר מוד עבור אקטיביטי מסויים ניתן להשתמש בסט לוקל נייט מוד
In activity - call prior to super() in onCreate()
דארק ט’ים זה אחלה.. אבל נשמע כמו ימבה עבודה
We won’t know exactly how this feature works until the source code for Android Q is released or Google publishes documentation for it, though.
בשביל להפעיל פורס דארק צריך להוסיף פורס דארק לטרו. בדוגמה כאן אני מפעילה פורס דארק בט’ים של האפליקציה ככה הוא יופעל בכל מקום באפליקציה. אבל אפשר לבחור איפה להפעיל אותו, ככה שאפשר להפעיל אותו רק עבור אקטיבי אחד או שניים.
כדאי לבדוק טוב טוב את האפליקציה לפני שמפעילים פורס דארק על כל האפליקציה. אם רואים דברים מוזרים אז פשוט משתמשים בפונקציות האלה וזה יסדר אתכם.
כדאי לבדוק טוב טוב את האפליקציה לפני שמפעילים פורס דארק על כל האפליקציה. אם רואים דברים מוזרים אז פשוט משתמשים בפונקציות האלה וזה יסדר אתכם.
הבאסה היא שפורסדארק עובד רק מאנדרואיד קיו. לכן כדאי לתמוך בדארק ט’ים ע”י יצירת קוסטום ט’ים. סיבה נוספת לייצירת קוסטום ט’ים היא שעם פורסדארק אין לנו שליטה איך המסך יראה
הדבר הבא שצריך לעשות הוא להגיד לאפקומפט באיזה מוד להשתמש. יש לנו שני אייפיי’ס לזה, הראשון סטדיפולט נייט מוד. הוא משמש כשרוצים להגדיר מוד עבור כל האפליקציה, כל האקטיביטיס. אם רוצים להגדיר מוד עבור אקטיביטי מסויים ניתן להשתמש בסט לוקל נייט מוד
בבלס הוא אלטרנטיבה לסיסטם אלרט ווינדו. הוא חדש בקיו כדבלופר פרויו, ניתן למצוא את זה בדוולופר אופשיינס. בגרסת בטה זה יפעל אוטומטית..
יכולים להתרחב ולחשוף פונקציונליות ומידע, ולהתכווץ חזרה. המסך המורחב זה בעצם אקטיביטי
יכולים להתרחב ולחשוף פונקציונליות ומידע, ולהתכווץ חזרה. המסך המורחב זה בעצם אקטיביטי
יכולים להתרחב ולחשוף פונקציונליות ומידע, ולהתכווץ חזרה. המסך המורחב זה בעצם אקטיביטי
אז בשביל בניית בבל צריך :
אחד, לבנות את האקטיביטי שמוצגת בבבל.
שתיים לבנות בבלמטאדאטה
שלוש, להוסיף את המטאדאטה לנוטיפיקציה ולשלוח
אז דבר ראשון צריך אקטיביטי שתהיה המצב האקספנדד של הבבל. ישנם שלושה דברים שחייבים להיות לאקטיביטי בשביל שהיא תהיה בבל - אמבדבל, דוקומנטלונצ’ מוד תמיד ורסייזאבל. אם אחד מאלה חסר אז הבבל יהיה פשוט נוטיפיקציה רגילה.
אז דבר ראשון צריך אקטיביטי שתהיה המצב האקספנדד של הבבל. ישנם שלושה דברים שחייבים להיות לאקטיביטי בשביל שהיא תהיה בבל - אמבדבל, דוקומנטלונצ’ מוד תמיד ורסייזאבל. אם אחד מאלה חסר אז הבבל יהיה פשוט נוטיפיקציה רגילה.
אז דבר ראשון צריך אקטיביטי שתהיה המצב האקספנדד של הבבל. ישנם שלושה דברים שחייבים להיות לאקטיביטי בשביל שהיא תהיה בבל - אמבדבל, דוקומנטלונצ’ מוד תמיד ורסייזאבל. אם אחד מאלה חסר אז הבבל יהיה פשוט נוטיפיקציה רגילה.
אז דבר ראשון צריך אקטיביטי שתהיה המצב האקספנדד של הבבל. ישנם שלושה דברים שחייבים להיות לאקטיביטי בשביל שהיא תהיה בבל - אמבדבל, דוקומנטלונצ’ מוד תמיד ורסייזאבל. אם אחד מאלה חסר אז הבבל יהיה פשוט נוטיפיקציה רגילה.
ואז יוצרים בבל מטאדאטה עם האייפיאי החדש. כל מה שצריך זה אייקון ואת הפנדינג אינטנט.
ואז יוצרים בבל מטאדאטה עם האייפיאי החדש. כל מה שצריך זה אייקון ואת הפנדינג אינטנט.
ואז יוצרים בבל מטאדאטה עם האייפיאי החדש. כל מה שצריך זה אייקון ואת הפנדינג אינטנט.
ואז יוצרים בבל מטאדאטה עם האייפיאי החדש. כל מה שצריך זה אייקון ואת הפנדינג אינטנט.
ואז בונים נוטיפיקציה כרגיל ורק מוסיפים את הבבל מטאדטה
ואז בונים נוטיפיקציה כרגיל ורק מוסיפים את הבבל מטאדטה
Q BETA 2
קצת פירוט על הכללים -
קצת פירוט על הכללים -
When a bubble is expanded, the content activity goes through the normal process lifecycle, resulting in the application becoming a foreground process (if not already).
When the bubble is collapsed or dismissed the activity will be destroyed. This may result in the process being cached and later killed, depending on whether the app has any other foreground components running.
.אז אני רוצה לנצל עכשיו רגע בשביל לשתף אתכם בחוויה להיות בגוגל איי או, פסטיבל ענק עם האנשים שלימדו אותי אנדרואיד בישראל, ועם האנשים שלימדו אותי אנדרואיד באינטרנט. היה פשוט אדיר.השנה בגוגל איי או גוגל שמו דגש מיוחד על הצורך והחשיבות בשיתוף .
גוגל מציגים שיירים שיט חדש. המכיל קונטנטפרוי - מידע על התוכן אותו רוצים לשתף, אפשר להוסיף תמונה ו/או טקסט
איי פי איי חדש שיירינג שורטקאטטס
העתק לקליפבורד
ושיפור ביצועים
אז קודם כל נדבר על האייפי איי החדש לשיירינג שורטקאט, בדוגמה כאן רואים 4 קיצורים אבל השרייניג שיט החדש תומך בעד 8 קיצורים חדשים
בשביל ליצור שיירינגשורטקאט צריך לבנות קישור דינאמי כרגיל, עם תוספת של שדה קטגוריה (חובה), וכדאי גם להוסיף שדה של פרסן. שיירינג שורטקאט חייב להיות מקושר עם אקטיביטי שתואמת את הקטגוריה , והאקטיביטי חייבת להיות בעלת אקשיין סנד אינטנט פילטר(מה שכבר קיים אם האקטיביטי שלכם כבר בשייר שיט)
בשביל ליצור שיירינגשורטקאט צריך לבנות קישור דינאמי כרגיל, עם תוספת של שדה קטגוריה (חובה), וכדאי גם להוסיף שדה של פרסן. שיירינג שורטקאט חייב להיות מקושר עם אקטיביטי שתואמת את הקטגוריה , והאקטיביטי חייבת להיות בעלת אקשיין סנד אינטנט פילטר(מה שכבר קיים אם האקטיביטי שלכם כבר בשייר שיט)
בשביל ליצור שיירינגשורטקאט צריך לבנות קישור דינאמי כרגיל, עם תוספת של שדה קטגוריה (חובה), וכדאי גם להוסיף שדה של פרסן. שיירינג שורטקאט חייב להיות מקושר עם אקטיביטי שתואמת את הקטגוריה , והאקטיביטי חייבת להיות בעלת אקשיין סנד אינטנט פילטר(מה שכבר קיים אם האקטיביטי שלכם כבר בשייר שיט)
בשביל ליצור שיירינגשורטקאט צריך לבנות קישור דינאמי כרגיל, עם תוספת של שדה קטגוריה (חובה), וכדאי גם להוסיף שדה של פרסן. שיירינג שורטקאט חייב להיות מקושר עם אקטיביטי שתואמת את הקטגוריה , והאקטיביטי חייבת להיות בעלת אקשיין סנד אינטנט פילטר(מה שכבר קיים אם האקטיביטי שלכם כבר בשייר שיט)
בשביל ליצור שיירינגשורטקאט צריך לבנות קישור דינאמי כרגיל, עם תוספת של שדה קטגוריה (חובה), וכדאי גם להוסיף שדה של פרסן. שיירינג שורטקאט חייב להיות מקושר עם אקטיביטי שתואמת את הקטגוריה , והאקטיביטי חייבת להיות בעלת אקשיין סנד אינטנט פילטר(מה שכבר קיים אם האקטיביטי שלכם כבר בשייר שיט)
בשביל ליצור שיירינגשורטקאט צריך לבנות קישור דינאמי כרגיל, עם תוספת של שדה קטגוריה (חובה), וכדאי גם להוסיף שדה של פרסן. שיירינג שורטקאט חייב להיות מקושר עם אקטיביטי שתואמת את הקטגוריה , והאקטיביטי חייבת להיות בעלת אקשיין סנד אינטנט פילטר(מה שכבר קיים אם האקטיביטי שלכם כבר בשייר שיט)
בשביל ליצור שיירינגשורטקאט צריך לבנות קישור דינאמי כרגיל, עם תוספת של שדה קטגוריה (חובה), וכדאי גם להוסיף שדה של פרסן. שיירינג שורטקאט חייב להיות מקושר עם אקטיביטי שתואמת את הקטגוריה , והאקטיביטי חייבת להיות בעלת אקשיין סנד אינטנט פילטר(מה שכבר קיים אם האקטיביטי שלכם כבר בשייר שיט)
בשביל ליצור שיירינגשורטקאט צריך לבנות קישור דינאמי כרגיל, עם תוספת של שדה קטגוריה (חובה), וכדאי גם להוסיף שדה של פרסן. שיירינג שורטקאט חייב להיות מקושר עם אקטיביטי שתואמת את הקטגוריה , והאקטיביטי חייבת להיות בעלת אקשיין סנד אינטנט פילטר(מה שכבר קיים אם האקטיביטי שלכם כבר בשייר שיט)
בשביל ליצור שיירינגשורטקאט צריך לבנות קישור דינאמי כרגיל, עם תוספת של שדה קטגוריה (חובה), וכדאי גם להוסיף שדה של פרסן. שיירינג שורטקאט חייב להיות מקושר עם אקטיביטי שתואמת את הקטגוריה , והאקטיביטי חייבת להיות בעלת אקשיין סנד אינטנט פילטר(מה שכבר קיים אם האקטיביטי שלכם כבר בשייר שיט)
ואיך מוסיפים מידע על התוכן שאנחנו רוצים לשתף
בשביל להוסיף תוכן משמעותי לשיירינג שיט צריך לעשות שיירינג אינטנט כרגיל.
ואז מוסיפים את המטאדאטה לפני הקריאה לקרייטצ’וזר.
שימו לב לתת את ההרשאות המתאימות בשביל שהמערכת תקרא את הט’מבנייל, אחרת התמונה לא תופיעה
בשביל להוסיף תוכן משמעותי לשיירינג שיט צריך לעשות שיירינג אינטנט כרגיל.
ואז מוסיפים את המטאדאטה לפני הקריאה לקרייטצ’וזר.
שימו לב לתת את ההרשאות המתאימות בשביל שהמערכת תקרא את הט’מבנייל, אחרת התמונה לא תופיעה
בשביל להוסיף תוכן משמעותי לשיירינג שיט צריך לעשות שיירינג אינטנט כרגיל.
ואז מוסיפים את המטאדאטה לפני הקריאה לקרייטצ’וזר.
שימו לב לתת את ההרשאות המתאימות בשביל שהמערכת תקרא את הט’מבנייל, אחרת התמונה לא תופיעה
בשביל להוסיף תוכן משמעותי לשיירינג שיט צריך לעשות שיירינג אינטנט כרגיל.
ואז מוסיפים את המטאדאטה לפני הקריאה לקרייטצ’וזר.
שימו לב לתת את ההרשאות המתאימות בשביל שהמערכת תקרא את הט’מבנייל, אחרת התמונה לא תופיעה