<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yazan Tarifi</title>
    <description>The latest articles on DEV Community by Yazan Tarifi (@yazan98).</description>
    <link>https://dev.to/yazan98</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F257895%2Fe1cacde4-036f-47f0-a90e-d27a1ce5329b.png</url>
      <title>DEV Community: Yazan Tarifi</title>
      <link>https://dev.to/yazan98</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yazan98"/>
    <language>en</language>
    <item>
      <title>Unleashing the Future: Building Chromecast TV Applications with Jetpack Compose and Material 3</title>
      <dc:creator>Yazan Tarifi</dc:creator>
      <pubDate>Fri, 01 Dec 2023 11:56:00 +0000</pubDate>
      <link>https://dev.to/yazan98/unleashing-the-future-building-chromecast-tv-applications-with-jetpack-compose-and-material-3-3hji</link>
      <guid>https://dev.to/yazan98/unleashing-the-future-building-chromecast-tv-applications-with-jetpack-compose-and-material-3-3hji</guid>
      <description>&lt;p&gt;In the ever-evolving landscape of digital entertainment, the ability to seamlessly cast content from your device to the big screen has become a ubiquitous and transformative experience. Chromecast, a revolutionary streaming device, has paved the way for immersive, cross-platform entertainment. Now, imagine harnessing the power of Jetpack Compose, Google’s modern UI toolkit, to elevate your Chromecast TV applications to new heights.&lt;/p&gt;

&lt;p&gt;As we celebrate the first anniversary of this groundbreaking combination, this tutorial embarks on a journey to demystify the process of creating Chromecast TV applications with Jetpack Compose. The fusion of Jetpack Compose’s declarative UI approach, Material You’s dynamic theming, and the Chromecast SDK offers a recipe for crafting visually stunning, user-friendly applications that seamlessly integrate with the Chromecast ecosystem.&lt;/p&gt;

&lt;p&gt;Throughout this tutorial, we will delve into the intricacies of the Chromecast SDK, exploring how Jetpack Compose can be leveraged to design intuitive user interfaces tailored for TV screens. Additionally, we’ll dive into the TV API and the innovative TV Compose library, exploring the synergy between these components and Material You to create applications that not only captivate but also adapt to users’ preferences.&lt;/p&gt;

&lt;p&gt;Whether you’re a seasoned developer or just starting your journey in the world of Android app development, this tutorial will provide a step-by-step guide, enriched with code snippets and practical examples, to empower you in building Chromecast TV applications that seamlessly integrate with the latest Material Design principles. Let’s embark on this exciting journey to redefine the future of Chromecast TV applications with Jetpack Compose and Material You.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxukd9342jge3injkbo3p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxukd9342jge3injkbo3p.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this article, we will begin the journey of designing the application that links with the TV application to broadcast an events between the two devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before We Start What we Need ?
&lt;/h2&gt;

&lt;p&gt;A Chromecast Device (External or Built in) inside The TV&lt;br&gt;
Android TV Device&lt;br&gt;
Chromecast Dashboard Ready with 5$ To open The Account&lt;br&gt;
The Chromecast Device Serial Number&lt;br&gt;
Both Mobile and TV Should be Connected on Same Internet Source&lt;br&gt;
To Get the Serial number of the Chromecast Device You can Find it on the Product Box in case of External Device, Internal Chromecast Can be found on OS Settings in the TV Under Chromecast Section&lt;/p&gt;

&lt;p&gt;In This Example We Will build 2 Applications (Sender, Receiver) and The Application Called LinkLoom with one Main Objective Which is Pushing Links Between Mobile and TV and The Reason about this was i want to Push Links Between My Personal Mobile to TV and to Achive this We Can use ChromeCast to Push These Links With one Click only&lt;/p&gt;

&lt;p&gt;Now We Need to Decide What is the Package Name of Each Application to Configure Them in the Chromecast Console and I’ll Use (com.yazantarifi.linkloom) So the Package names Will be (com.yazantarifi.linkloom.tv, com.yazantarifi.linkloom.mobile)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo98uzgavtiwdmmnhwtvw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo98uzgavtiwdmmnhwtvw.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After Registering the Application With the Following Configurations You need to Add the Application Name and Publish It Then Wait Until App is Published&lt;/p&gt;

&lt;p&gt;Now We Need to Register Our Chromecast Device for Testing&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmi3866yv1o4cgy890c9h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmi3866yv1o4cgy890c9h.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill This Dialog with the Device Serial number then Submit it and wait Until being Ready for Testing&lt;/p&gt;

&lt;p&gt;We Will Build the TV Application First with the Following Dependencies inside TV Module&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.leanback:leanback:1.0.0")

// Compose and Glide to Load Images
implementation("com.github.bumptech.glide:compose:1.0.0-beta01")
implementation(platform("androidx.compose:compose-bom:2023.10.01"))

// Jetoack Compose
implementation("androidx.activity:activity-compose:1.8.0")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.lifecycle:lifecycle-process:2.6.2")
debugImplementation("androidx.compose.ui:ui-tooling")

// Compose TV Dependencies
implementation("androidx.tv:tv-foundation:1.0.0-alpha10")
implementation("androidx.tv:tv-material:1.0.0-alpha10")

// Chromecast SDK
implementation("com.google.android.gms:play-services-cast-tv:21.0.0")
implementation("com.google.android.gms:play-services-cast:21.3.0")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Creating the TV Home Screen Design We Will depend on the Following Composables&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;NavigationDrawer&lt;/li&gt;
&lt;li&gt;NavigationDrawerItem&lt;/li&gt;
&lt;li&gt;TvLazyColumn&lt;/li&gt;
&lt;li&gt;GlideImage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can Follow the &lt;a href="https://developer.android.com/training/tv/playback/compose" rel="noopener noreferrer"&gt;Android Documentation&lt;/a&gt; about how to Use Compose on TV Devices and &lt;a href="https://bumptech.github.io/glide/int/compose.html" rel="noopener noreferrer"&gt;Glide for Compose Apps&lt;/a&gt; but Our Focus now is on Chromecast SDK With Compose Apps&lt;/p&gt;

&lt;p&gt;Now The Home Screen Design Implemented Like The Following Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class HomeScreen: ComponentActivity() {

    private val viewModel: ApplicationViewModel by viewModels()

    @OptIn(ExperimentalTvMaterial3Api::class, ExperimentalGlideComposeApi::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LinkLoomTheme {
                val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
                var selectedNavigationIndex = remember { mutableIntStateOf(0) }
                NavigationDrawer(
                    drawerState = drawerState,
                    modifier = Modifier
                        .fillMaxHeight()
                        .background(Color.Black)
                        .padding(10.dp),
                    drawerContent = {
                        Column(
                            modifier = Modifier
                                .fillMaxHeight()
                                .padding(10.dp),
                            verticalArrangement = Arrangement.Center,
                            horizontalAlignment = Alignment.CenterHorizontally
                        ) {
                            NavigationItemComposable(drawerState, 0, NavigationItem(R.drawable.home, getString(R.string.home)), selectedNavigationIndex)
                            NavigationItemComposable(drawerState, 1, NavigationItem(R.drawable.history, getString(R.string.history)), selectedNavigationIndex)
                            NavigationItemComposable(drawerState, 2, NavigationItem(R.drawable.settings, getString(R.string.settings)), selectedNavigationIndex)
                        }
                    }
                ) {
                    ScreenContent(selectedNavigationIndex.intValue)
                }
            }
        }
    }

    @OptIn(ExperimentalTvMaterial3Api::class)
    @Composable
    fun ScreenContent(position: Int) {
        Column(modifier = Modifier
            .fillMaxSize()
            .padding(10.dp)
        ) {
            when (position) {
                0 -&amp;gt; HomeTabComposable()
                1 -&amp;gt; HistoryTabComposable(viewModel)
                2 -&amp;gt; SettingsComposable()
            }
        }
    }

    @OptIn(ExperimentalTvMaterial3Api::class, ExperimentalGlideComposeApi::class)
    @Composable
    fun NavigationDrawerScope.NavigationItemComposable(state: DrawerState, index: Int, item: NavigationItem, selectedNavigationIndex: MutableIntState) {
        val isSelected = selectedNavigationIndex.intValue == index
        val scope = rememberCoroutineScope()

        NavigationDrawerItem(
            selected = isSelected,
            onClick = {
                selectedNavigationIndex.intValue = index
                scope.launch {
                    state.setValue(DrawerValue.Closed)
                }
            },
            leadingContent = {
                GlideImage(model = item.icon, contentDescription = item.text, colorFilter = ColorFilter.tint(Color.White))
            },
            modifier = Modifier.padding(10.dp),
            colors = NavigationDrawerItemColors(
                focusedContainerColor = RedPrimary,
                focusedContentColor = Color.White,
                focusedSelectedContainerColor = RedPrimaryDark,
                focusedSelectedContentColor = Color.White,
                selectedContainerColor = RedPrimaryDark, // done
                selectedContentColor = Color.White,
                containerColor = Color.Black,
                contentColor = Color.White,
                inactiveContentColor = Color.White,
                pressedContainerColor = Color.Black,
                pressedContentColor = Color.White,
                disabledContainerColor = Color.Black,
                disabledContentColor = Color.Black,
                disabledInactiveContentColor = Color.Black,
                pressedSelectedContainerColor = Color.Black,
                pressedSelectedContentColor = Color.White
            )
        ) {
            Text(text = item.text, color = Color.White, softWrap = false)
        }
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After Finishing the Tv Application In Home Screen Design Now we Need to Configure it to be able to Receive Events&lt;/p&gt;

&lt;p&gt;We Will build a Class to Configure the Settings for Chromecast Options Provider like the Following Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import android.content.Context
import com.google.android.gms.cast.LaunchOptions
import com.google.android.gms.cast.tv.CastReceiverOptions
import com.google.android.gms.cast.tv.ReceiverOptionsProvider

class MyReceiverOptionsProvider : ReceiverOptionsProvider {
    override fun getOptions(context: Context): CastReceiverOptions {
        return CastReceiverOptions.Builder(context)
            .setStatusText("LinkLoom Cast Connect")
            .setCustomNamespaces(listOf("urn:x-cast:open-website"))
            .build()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our App Depends on the Custom Namespaces so we need to register them in the Configurations Class and each Namespace Should have (urn:x-cast:) before the name then feel free to add any name you want&lt;/p&gt;

&lt;p&gt;Now we Need to Register The Configurations in the Manifest File Like the Following Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;activity
    android:name=".MainScreen"
    android:banner="@drawable/banner_icon"
    android:exported="true"
    android:icon="@drawable/banner_icon"
    android:label="@string/app_name"
    android:logo="@drawable/banner_icon"
    android:screenOrientation="landscape"&amp;gt;
        &amp;lt;intent-filter&amp;gt;
            &amp;lt;action android:name="android.intent.action.MAIN" /&amp;gt;
            &amp;lt;category android:name="android.intent.category.LEANBACK_LAUNCHER" /&amp;gt;
        &amp;lt;/intent-filter&amp;gt;

        &amp;lt;intent-filter&amp;gt;
            &amp;lt;action android:name="com.google.android.gms.cast.tv.action.LAUNCH" /&amp;gt;
            &amp;lt;category android:name="android.intent.category.DEFAULT" /&amp;gt;
        &amp;lt;/intent-filter&amp;gt;
&amp;lt;/activity&amp;gt;

 &amp;lt;meta-data
      android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
      android:value="{AppPackageName}.MyReceiverOptionsProvider" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace {AppPackageName} with Your Application Package Name for Our Example the Package Name is com.yazantarifi.linkloom.tv so the Result will be com.yazantarifi.linkloom.tv.MyReceiverOptionsProvider&lt;/p&gt;

&lt;p&gt;Now we Need to Init The Chromecast SDK in Our Application Class like the Following Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CastReceiverContext.initInstance(this)
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver())
CastReceiverContext.getInstance().registerEventCallback(EventCallback())

class AppLifecycleObserver : DefaultLifecycleObserver {
        override fun onResume(owner: LifecycleOwner) {
            CastReceiverContext.getInstance().start()
        }

        override fun onPause(owner: LifecycleOwner) {
            CastReceiverContext.getInstance().stop()
        }
}

private inner class EventCallback : CastReceiverContext.EventCallback() {
        override fun onSenderConnected(senderInfo: SenderInfo) {
            Toast.makeText(
                this@LinkLoomApplication,
                "Sender connected " + senderInfo.senderId,
                Toast.LENGTH_LONG)
                .show()
        }

        override fun onSenderDisconnected(eventInfo: SenderDisconnectedEventInfo) {
            Toast.makeText(
                this@LinkLoomApplication,
                "Sender disconnected " + eventInfo.senderInfo.senderId,
                Toast.LENGTH_LONG)
                .show()
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now on Our Activity Registered in the Manifest We can Receive the Connection Events between Mobile and TV inside onNewIntent Function Like the Following Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;override fun onNewIntent(intent: Intent?) {
   super.onNewIntent(intent)
   processIntent(intent)
}

fun processIntent(intent: Intent?) {
    Toast.makeText(this, "New Event 1", Toast.LENGTH_SHORT).show()
    val mediaManager: MediaManager = CastReceiverContext.getInstance().mediaManager
    // Pass intent to Cast SDK
    if (mediaManager.onNewIntent(intent)) {
         return
    }

    // Clears all overrides in the modifier.
    mediaManager.mediaStatusModifier.clear()
    Toast.makeText(this, "New Event", Toast.LENGTH_SHORT).show()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After Establishing the Connection Now we Need to Register the Custom Event that We need to Open the Urls Coming from Mobile App&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CastReceiverContext.getInstance().setMessageReceivedListener("urn:x-cast:open-website", object : MessageReceivedListener {
   override fun onMessageReceived(p0: String, p1: String?, p2: String) {
        Toast.makeText(this@HomeScreen, "Opening New Website : ${p2}", Toast.LENGTH_SHORT).show()
        if (!TextUtils.isEmpty(p2)) {
             WebsiteScreen.startScreenDirectly(this@HomeScreen, p2, "Custom Website")
        }
   }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the Name (urn:x-cast:open-website) Should be The same Name Registered in the Provider Settings Class that We Registered in Our Manifest, now in this Callback We Can Trigger the Event and The TV Example Done Now We Will Start Building The Mobile Application&lt;/p&gt;

&lt;p&gt;Gradle Dependencies That We Need for Mobile Device&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;implementation(platform("com.google.firebase:firebase-bom:31.4.0"))
implementation(platform("androidx.compose:compose-bom:2023.10.01"))

implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.mediarouter:mediarouter:1.6.0")
implementation("com.google.android.gms:play-services-cast-framework:21.3.0")

implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.activity:activity-compose:1.8.0")
implementation("com.github.bumptech.glide:compose:1.0.0-beta01")

implementation("androidx.compose.material3:material3")
implementation("androidx.compose.foundation:foundation")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside Application Class we Need to Init the SDK for Chromecast and Be aware that This Function Can Throw Exceptions on Some Devices if they Cant Use Chromecast SDK Like Emulators So We Need to Test Always on Real Devices&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CastContext
      .getSharedInstance(this, Executors.newSingleThreadExecutor())
      .addOnSuccessListener { castContext: CastContext? -&amp;gt;
            println("Chromecast Successful")
            castInstance = castContext
      }
      .addOnFailureListener { exception: java.lang.Exception? -&amp;gt;
            println("Chromecast Exception : ${exception?.message}")
            exception?.let { FirebaseCrashlytics.getInstance().recordException(it) }
      }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now We Will Configure the Mobile App Class for Casting&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import android.content.Context
import com.google.android.gms.cast.LaunchOptions
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.SessionProvider

class CastOptionsProvider : OptionsProvider {
    override fun getCastOptions(context: Context): CastOptions {
        val options = LaunchOptions.Builder()
            .setAndroidReceiverCompatible(true)
            .build()

        return CastOptions.Builder()
            .setReceiverApplicationId("07EB8A95")
            .setLaunchOptions(options)
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List&amp;lt;SessionProvider&amp;gt;? {
        return null
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;07EB8A95 Is the ID you get when you publish your Application in the First Step from the Console, make sure to replace it with your id&lt;/p&gt;

&lt;p&gt;Now We Register the Configuration Class in Manifest&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;meta-data
    android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
    android:value="com.yazantarifi.mobile.CastOptionsProvider" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now When we Open the Screen We Need to Search on All Devices Available on the Same Network&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mediaSelector = MediaRouteSelector.Builder()
            .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
            .addControlCategory(CastMediaControlIntent.categoryForCast("07EB8A95"))
            .build()

mediaRouter = MediaRouter.getInstance(applicationContext)
val routes = mediaRouter.routes;
routes.forEach {
     val device = CastDevice.getFromBundle(it.extras)
     if (!TextUtils.isEmpty(device?.deviceId ?: "")) {
          chromecastDevicesState.add(LinkLoomChromeCastDevice(device?.deviceId ?: "", device?.friendlyName ?: "", device?.deviceVersion ?: "", it))
     }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now We Need to Establish a Connection between Mobile App and Tv and this can be done by using the select Route Function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mediaSelector?.let { it1 -&amp;gt;
      mediaRouter.addCallback(
      it1, mediaRouterCallback,
      MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN)
}
Toast.makeText(this@HomeScreen, "Device Connection Started", Toast.LENGTH_SHORT).show()
mediaRouter.selectRoute(it.routeDevice)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After Connection Success Now We have a Session between both of them we need to store the Session in a Variable to Use it to push Events, Now We Have all of the Available Devices, We Need to Push the Event for the Device&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;castSession?.sendMessage("urn:x-cast:open-website", websiteLink)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(urn:x-cast:open-website) This Name is the Name we registered to listen to events and get the payload, replace it with your Name&lt;/p&gt;

&lt;p&gt;Now We Will see a Full Example of a Functinal Activity that Interact with Chromecast and Send Messages&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class HomeScreen: ComponentActivity() {

    private var currentUrl = ""
    private var connectedDevice: String = ""
    private var castSession: CastSession? = null
    private lateinit var mediaRouter: MediaRouter
    private var mediaSelector: MediaRouteSelector? = null
    private var connectedDeviceState by mutableStateOf("")
    private val chromecastDevicesState by lazy { mutableStateListOf&amp;lt;LinkLoomChromeCastDevice&amp;gt;() }

    @OptIn(ExperimentalMaterial3Api::class, ExperimentalGlideComposeApi::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .background(Color.Black)
                    .padding(30.dp),
                verticalArrangement = Arrangement.SpaceBetween,
                horizontalAlignment = Alignment.Start
            ) {
                Column(modifier = Modifier.fillMaxWidth()) {
                    Text(text = "Link Loom Mobile Application", color = RedPrimary)
                    Spacer(modifier = Modifier.height(10.dp))
                    Text(text = "Share Websites Links Directly to TV", color = Color.White)
                }

                Column(modifier = Modifier.fillMaxWidth()) {
                    var websiteLink by remember { mutableStateOf("") }
                    OutlinedTextField(modifier = Modifier.fillMaxWidth(), value = websiteLink, onValueChange = {
                        websiteLink = it
                    }, placeholder = {
                        Text(text = "Example (www.example.com)")
                    }, label = {
                        Text(text = "Website Url", color = RedPrimary)
                    }, colors = TextFieldDefaults.textFieldColors(focusedTextColor = Color.Black))

                    Spacer(modifier = Modifier.height(20.dp))
                    Button(
                        modifier = Modifier,
                        onClick = {
                            if (websiteLink.isNotEmpty()) {
                                currentUrl = websiteLink
                                castSession?.sendMessage("urn:x-cast:open-website", websiteLink)
                            }
                        }
                    ) {
                        Text(text = getString(R.string.add_new), color = Color.White)
                    }

                    Spacer(modifier = Modifier.height(20.dp))
                    Text(text = "Select a Device To Connect", color = Color.White)
                    Spacer(modifier = Modifier.height(5.dp))
                    LazyColumn(modifier = Modifier.fillMaxWidth()) {
                        items(chromecastDevicesState) {
                            Column( modifier = Modifier
                                .fillMaxWidth()
                                .clickable {
                                    if (connectedDeviceState.isEmpty()) {
                                        mediaSelector?.let { it1 -&amp;gt;
                                            mediaRouter.addCallback(
                                                it1, mediaRouterCallback,
                                                MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN)
                                        }
                                        Toast.makeText(this@HomeScreen, "Device Connection Started", Toast.LENGTH_SHORT).show()
                                        mediaRouter.selectRoute(it.routeDevice)
                                    }
                                }) {
                                Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Start) {

                                    GlideImage(model = R.drawable.chromecast, contentDescription = "Device Icon", modifier = Modifier.size(30.dp))
                                    Spacer(modifier = Modifier.width(10.dp))
                                    Column {
                                        Text(text = it.name, color = Color.White, modifier = Modifier
                                            .fillMaxWidth()
                                            .padding(5.dp))
                                        Text(text = it.id, color = Color.White, modifier = Modifier
                                            .fillMaxWidth())
                                    }
                                }

                                Spacer(modifier = Modifier.height(10.dp))
                                Divider()
                            }
                        }
                    }
                }

                var result by remember { mutableStateOf(connectedDeviceState) }
                LaunchedEffect(connectedDeviceState) {
                    result = connectedDeviceState
                }

                Column(modifier = Modifier.fillMaxWidth()) {
                    if (result.isNotEmpty()) {
                        Text(text = "Chromecast Connected", color = Color.Green)
                    }
                    Spacer(modifier = Modifier.height(5.dp))
                    Text(text = "Link Loom Work only with Link Loom TV Application", color = Color.White)
                    Spacer(modifier = Modifier.height(10.dp))
                    Text(text = "Make Sure All Devices Connected to Same Network", color = Color.Gray)
                }
            }
        }


        mediaSelector = MediaRouteSelector.Builder()
            .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
            .addControlCategory(CastMediaControlIntent.categoryForCast("07EB8A95"))
            .build()

        mediaRouter = MediaRouter.getInstance(applicationContext)
        val routes = mediaRouter.routes;
        routes.forEach {
            val device = CastDevice.getFromBundle(it.extras)
            if (!TextUtils.isEmpty(device?.deviceId ?: "")) {
                chromecastDevicesState.add(LinkLoomChromeCastDevice(device?.deviceId ?: "", device?.friendlyName ?: "", device?.deviceVersion ?: "", it))
            }
        }

        (applicationContext as LinkLoomApplication).castInstance?.sessionManager?.addSessionManagerListener(sessionManagerListener, CastSession::class.java)
    }

    override fun onStart() {
        super.onStart()
        mediaSelector?.also { selector -&amp;gt;
            mediaRouter.addCallback(selector, mediaRouterCallback,
                MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN)
        }
    }

    private val mediaRouterCallback = object : MediaRouter.Callback() {
        override fun onRouteSelected(
            router: MediaRouter,
            route: MediaRouter.RouteInfo,
            reason: Int
        ) {
            super.onRouteSelected(router, route, reason)
            println("Charomcast Event : onRouteSelected : $route")
        }
    }

    private val sessionManagerListener = object : SessionManagerListener&amp;lt;CastSession&amp;gt; {
        override fun onSessionEnded(p0: CastSession, p1: Int) {
            println("Charomcast Event : onSessionEnded")
            connectedDevice = ""
            connectedDeviceState = ""
        }

        override fun onSessionEnding(p0: CastSession) {
            println("Charomcast Event : onSessionEnding")
            connectedDevice = ""
            connectedDeviceState = ""
        }

        override fun onSessionResumeFailed(p0: CastSession, p1: Int) {
            println("Charomcast Event : onSessionResumeFailed")
        }

        override fun onSessionResumed(p0: CastSession, p1: Boolean) {
            println("Charomcast Event : onSessionResumed")
        }

        override fun onSessionResuming(p0: CastSession, p1: String) {
            println("Charomcast Event : onSessionResuming")
        }

        override fun onSessionStartFailed(p0: CastSession, p1: Int) {
            println("Charomcast Event : onSessionStartFailed : ${ (applicationContext as LinkLoomApplication).castInstance?.getCastReasonCodeForCastStatusCode(p1)}")
            println("Charomcast Event : onSessionStartFailed : ${CastStatusCodes.getStatusCodeString(p1)}")
            connectedDevice = ""
            connectedDeviceState = ""
        }

        override fun onSessionStarted(p0: CastSession, p1: String) {
            println("Charomcast Event : onSessionStarted")
            castSession = p0
            connectedDeviceState = p0.sessionId ?: ""
            Toast.makeText(this@HomeScreen, "Device Connected", Toast.LENGTH_SHORT).show()
        }

        override fun onSessionStarting(p0: CastSession) {
            println("Charomcast Event : onSessionStarting")
        }

        override fun onSessionSuspended(p0: CastSession, p1: Int) {
            println("Charomcast Event : onSessionSuspended")
        }

    }

    override fun onPause() {
        super.onPause()
        (applicationContext as LinkLoomApplication).castInstance?.sessionManager?.removeSessionManagerListener(sessionManagerListener, CastSession::class.java)
    }

    override fun onResume() {
        super.onResume()
        (applicationContext as LinkLoomApplication).castInstance?.sessionManager?.addSessionManagerListener(sessionManagerListener, CastSession::class.java)
        castSession = (applicationContext as LinkLoomApplication).castInstance?.sessionManager?.currentCastSession
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have Everything Done for Mobile and Tv, We Will See how to Run the Project&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Export 2 Apks for Mobile and Tv&lt;/li&gt;
&lt;li&gt;Push the Tv Apk on Tv and Install it&lt;/li&gt;
&lt;li&gt;Install Mobile SDK&lt;/li&gt;
&lt;li&gt;Enable Developer Mode in Tv Settings&lt;/li&gt;
&lt;li&gt;Turn Off The Tv Completely then Turn it on Again After Setup the Serial Number on the Dashboard and the Status is Ready for Testing&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Common Connection Mistakes
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Device Not Visible in Routes List: Not Connected on the Same Network&lt;/li&gt;
&lt;li&gt;Device Not Compatible to Accept Events, Mobile App Should Declare setAndroidReceiverCompatible to true in Configurations&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally We Have Completed the Setup for the Project and We have a Public Version of the Source Code available&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Yazan98/LinkLoomTv" rel="noopener noreferrer"&gt;Github Link&lt;/a&gt;&lt;/p&gt;

</description>
      <category>chromecast</category>
      <category>android</category>
      <category>compose</category>
      <category>tv</category>
    </item>
    <item>
      <title>How To Build Scheduled Task on Github Libraries Releases via Slack Apps and NodeJs</title>
      <dc:creator>Yazan Tarifi</dc:creator>
      <pubDate>Thu, 21 Oct 2021 20:06:33 +0000</pubDate>
      <link>https://dev.to/yazan98/how-to-build-scheduled-task-on-github-libraries-releases-via-slack-apps-2d15</link>
      <guid>https://dev.to/yazan98/how-to-build-scheduled-task-on-github-libraries-releases-via-slack-apps-2d15</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;In this Article, We Will build a Slack Application To Send Messages to Slack Channel when Any Library you follow on Github Publish New Release&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Description
&lt;/h2&gt;

&lt;p&gt;As a Developer, you worked on a lot of projects and inside these projects, you should use Libraries to implement a Feature whatever if the library is a 3rd part library or native library from the Framework itself and this is totally fine, The Problem I faced when I use The Libraries that I should check or Follow Someone on Twitter, Reddit or medium to get notifications on the Libraries that I'm using inside my Project, but if I didn't open any application from social media apps I will never know if any library pushed new Version on their Repository or maybe I know about this updates after 2 weeks and for this reason I need to get Notifications in the same day of the Release because some libraries are still pushing major release changes and it's really a big problem if we discover this Updates after 2 Weeks from the Release date&lt;/p&gt;

&lt;h2&gt;
  
  
  The Simplest Solution to Build Scheduler for This Process
&lt;/h2&gt;

&lt;p&gt;We should Create a Scheduled Task to check on all libraries that we are using inside our projects to get notifications on the same day inside this release and we gonna build it From Scratch with some tools that will help us build this Task&lt;/p&gt;

&lt;h2&gt;
  
  
  The Components Used inside This Project
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Node Js Project&lt;/li&gt;
&lt;li&gt;Slack Application&lt;/li&gt;
&lt;li&gt;The Source Links of The Libraries&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;The Full Example will be Available at the End of the Article&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  First Thing is to Build The Backend Project
&lt;/h2&gt;

&lt;p&gt;We Will Use NodeJs to Build Backend Side of this Project and Especially NestJs Framework and Typescript and We Need to Use one of the Backend Frameworks To Use Cron Jobs and CronJob is a Scheduled Event That Will Trigger Some Actions in Specific Time that You Specify it when Create the instance of the Task Service&lt;/p&gt;

&lt;p&gt;You can Use any Backend Framework because most of them has Cron Job implemented inside each one of them but for me, I Prefer to Build these Things in NestJs&lt;/p&gt;

&lt;h2&gt;
  
  
  Second Thing is to Create a Slack Application
&lt;/h2&gt;

&lt;p&gt;Slack Application is a Ready Api from Slack To Create Application with Id, Name, Logo That will Send Messages To Members, Channels inside your Workspace and for this project, we will configure this application to send messages with the New Versions of the Libraries on specific Channel&lt;/p&gt;

&lt;h2&gt;
  
  
  The final Part is Configuring The Source of Libraries
&lt;/h2&gt;

&lt;p&gt;This is Really Important is to Know each Library which source is the Best to Fetch it, For Example, when I build Android Applications I have multiple Sources to Fetch Libraries not all of them from one Source like (MavenCentral, GoogleMavenRepository, GithubRepository, GradlePortal) and We Need to Find a way to Fetch The Libraries from Multiple Sources inside the Same Project&lt;br&gt;
But In this Part, I saw Something Common Between all of them is 90% of the Libraries Source code inside Github Repositories and all of them has Releases and Tags Version so We Can Track all of them from a Common Source which is (Github API)&lt;/p&gt;

&lt;p&gt;Now Let's Start with The Implementation of the Project and We Will Start with Creating Slack and Github Configuration&lt;/p&gt;

&lt;p&gt;The First Step is To Configure Slack and Github to Get Tokens, Keys that we Need to Use inside our NodeJs Project&lt;/p&gt;

&lt;p&gt;First Step Create Slack Application inside Your Workspace and Specify the Logo and Name of the Application Then Add The Following Configuration inside App Manifest&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_metadata:
  major_version: 1
  minor_version: 1
display_information:
  name: Zilon
features:
  app_home:
    home_tab_enabled: true
    messages_tab_enabled: true
    messages_tab_read_only_enabled: false
  bot_user:
    display_name: Zilon
    always_online: true
oauth_config:
  redirect_urls:
    - https://example.com/slack/auth
  scopes:
    bot:
      - commands
      - chat:write
      - chat:write.public
settings:
  org_deploy_enabled: false
  socket_mode_enabled: false
  token_rotation_enabled: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now You Need to Create a Github Application from OAuth Settings Inside Your Github Account Settings and Take the Client Id and Secret Client Id then Save Them on Text File with Slack Keys (Token, Signing Key) and All of these Keys can be Found inside the Application Settings In General Tab Then Save All Keys and Tokens in One Text File because We Will Need them Later&lt;/p&gt;

&lt;p&gt;Now Create Channel inside Your Slack Workplace and Invite the Application you created inside this channel to get access to the Channel&lt;/p&gt;

&lt;h3&gt;
  
  
  Now Create NestJs Project
&lt;/h3&gt;

&lt;p&gt;Generate New project with NestJs By Executing the Following Commands inside Your Terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -g @nestjs/cli
npx nest new project-name

cd project-name
npm install --save @nestjs/schedule
npm install --save-dev @types/cron
npm install axios
npm install @slack/bolt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Now We Want to Add Cron Job to start Scheduled Task
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;This Task will be started at a specific time like the following example&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Injectable } from "@nestjs/common";
import { Cron, CronExpression } from "@nestjs/schedule";

@Injectable()
export class TasksService {

  @Cron(CronExpression.EVERY_DAY_AT_1AM, {
    name: "dependencies"
  })
  handleCron() {
   // Handle Libraries Checks
  }

}

// Now Declare this TaskService inside your App Module
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ScheduleModule } from '@nestjs/schedule';
import { TasksService } from "./task/TasksService";

@Module({
  imports: [ScheduleModule.forRoot()],
  controllers: [AppController],
  providers: [AppService, TasksService],
})
export class AppModule {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now We Will Use Axios To Send API Requests on GitHub to check all Libraries and get Releases Using GitHub API v3&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import axios, { Axios } from "axios";

export class NetworkInstance {

  public static SUCCESS_RESPONSE_CODE = 200;

  // General Url's For Requests
  public static GROUP_ARTIFACTS = "/group-index.xml";
  public static GITHUB_REPOS_KEY = "/repos/";
  public static GITHUB_RELEASES_KEY = "/git/refs/tags";

  public static getGithubRepositoriesInstance(): Axios {
    let instance = axios.create({
      timeout: 5000,
      baseURL: "https://api.github.com",
      responseType: "json",
      headers: { Accept: "application/json" }
    });

    instance.interceptors.request.use(request =&amp;gt; {
      console.log("Github Starting Request", request.url);
      return request;
    });

    return instance;
  }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the Functionality will be like the Following, We want to Store all libraries that we need to check every day then we will store the latest released tag and on each day the scheduler will send a request to the GitHub repo to check the latest tag if not similar to stored tag then we will send a slack message with this library&lt;/p&gt;

&lt;p&gt;In this stage, you have the option to store all of them in the way you like if you want you can use the database to store all of them but I prefer to write all of them inside JSON file in this type of project&lt;/p&gt;

&lt;p&gt;This is a Simple Example of how to check all of them in this stage you will need to get Github app clientId, SecreteId from the GitHub app that you created in your GitHub profile settings&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export class GithubDependenciesManager {

  private static GITHUB_LIBRARIES_FILE = "github-libraries.json";
  private static CONSOLE_LOGGING_KEY = "[Github Dependencies Manager]";
  private static GITHUB_CACHE_FILE = "github-libraries-cache.json";
  private static CONFIG_FILE = "config.json";

  /**
   * Main Method to Start inside This Manager
   * 1. Create and Validate the Local Json Files
   * 2. Start Validating The Old Files if Exists, if Not Will Create Default Files
   * 3. Will loop on all of them to see if the current version on github is similar to cached version
   * if not will send message on slack channel via config.json token, channelId
   */
  public async validateGithubLibrariesFile() {
    const fs = require("fs");
    this.createGithubLibrariesFile();

    let configFile = new ApplicationConfigFile("", "", "", true, "", "");
    if (fs.existsSync(GithubDependenciesManager.CONFIG_FILE)) {
      const dataFile = fs.readFileSync(GithubDependenciesManager.CONFIG_FILE);
      configFile = JSON.parse(dataFile.toString());
    }

    let librariesInformation = new Array&amp;lt;GithubRepositoriesInformation&amp;gt;();
    let librariesFile = new GithubContainerFileContent(new Array&amp;lt;GithubLibrary&amp;gt;());
    if (fs.existsSync(GithubDependenciesManager.GITHUB_LIBRARIES_FILE)) {
      const data = fs.readFileSync(GithubDependenciesManager.GITHUB_LIBRARIES_FILE, "utf8");
      librariesFile = JSON.parse(data);
      for (let i = 0; i &amp;lt; librariesFile.libraries.length; i++) {
        const library = librariesFile.libraries[i];
        await timer(5000);
        await NetworkInstance.getGithubRepositoriesInstance().get&amp;lt;Array&amp;lt;GithubRepositoryRelease&amp;gt;&amp;gt;(this.getGithubRequestUrl(configFile, NetworkInstance.GITHUB_REPOS_KEY + library.url + NetworkInstance.GITHUB_RELEASES_KEY), {
          method: "get"
        }).then((response) =&amp;gt; {
          if (response.status == NetworkInstance.SUCCESS_RESPONSE_CODE) {
            librariesInformation.push({
              name: library.name,
              url: library.url,
              releases: response.data
            });
          } else {
            console.error(GithubDependenciesManager.CONSOLE_LOGGING_KEY + " Exception : " + response.data + " Response : " + response.statusText);
          }
        }).catch((exception) =&amp;gt; {
          console.error(GithubDependenciesManager.CONSOLE_LOGGING_KEY + " Exception : " + exception);
        });
      }

      this.validateGithubRepositoriesReleasesVersions(librariesInformation);
    }
  }

  private getGithubRequestUrl(config: ApplicationConfigFile, url: string): string {
    return url + "?client_id=" + config.githubClientId + "&amp;amp;client_secret=" + config.githubClientSecrete;
  }

  /**
   * After Get all Releases From Github Api to Get All  Releases Information
   * We Will Validate the First Release With The Cached Versions if Not Equals
   * Will Send Slack Message with The New Version Triggered ...
   * @param libraries
   * @private
   */
  private validateGithubRepositoriesReleasesVersions(libraries: Array&amp;lt;GithubRepositoriesInformation&amp;gt;) {
    const fs = require("fs");
    let librariesFile = new GithubLibrariesCacheContainer(new Array&amp;lt;GithubCacheLibrary&amp;gt;());
    const requireUpdateLibraries = new Array&amp;lt;LibraryUpdateModel&amp;gt;();
    fs.readFile(GithubDependenciesManager.GITHUB_CACHE_FILE, "utf8", function readFileCallback(err, data) {
      if (err) {
        console.log(err);
      } else {
        librariesFile = JSON.parse(data);
        for (let i = 0; i &amp;lt; librariesFile.libraries.length; i++) {
          const cachedLibrary = librariesFile.libraries[i];
          for (let j = 0; j &amp;lt; libraries.length; j++) {
            const triggeredLibrary = libraries[j];
            if (cachedLibrary.name.includes(triggeredLibrary.name) &amp;amp;&amp;amp; triggeredLibrary.releases != null) {
              if (!cachedLibrary.release.includes(triggeredLibrary.releases[triggeredLibrary.releases.length - 1].ref.replace("refs/tags/", ""))) {
                console.log(GithubDependenciesManager.CONSOLE_LOGGING_KEY + " Library Need Update : " + triggeredLibrary.name + " Version : " + cachedLibrary.release + " Updated Version : " + triggeredLibrary.releases[triggeredLibrary.releases.length - 1].ref.replace("refs/tags/", ""));
                requireUpdateLibraries.push({
                  isGithubSource: true,
                  releaseUrl: "https://github.com/" + triggeredLibrary.url + "/releases",
                  version: triggeredLibrary.releases[triggeredLibrary.releases.length - 1].ref.replace("refs/tags/", ""),
                  url: "https://github.com/" + triggeredLibrary.url,
                  artifact: "",
                  groupId: "",
                  name: triggeredLibrary.url.split("/")[1]
                });
              }
            }
          }
        }

        new MessagingManager().sendMessageUpdateDependencies(requireUpdateLibraries);
        GithubDependenciesManager.saveNewGithubRepositoriesCacheFile(libraries);
      }
    });
  }

  /**
   * After Updating the Required Dependencies and Send All of them inside Messages in Slack
   * Now we Want to Refresh the Json File with New Cached Data
   * To Save The Notified Releases
   * @param libraries
   * @private
   */
  private static saveNewGithubRepositoriesCacheFile(libraries: Array&amp;lt;GithubRepositoriesInformation&amp;gt;) {
    const fs = require("fs");
    if (fs.existsSync(GithubDependenciesManager.GITHUB_CACHE_FILE)) {
      const librariesFile = new GithubLibrariesCacheContainer(new Array&amp;lt;GithubCacheLibrary&amp;gt;());
      for (let i = 0; i &amp;lt; libraries.length; i++) {
        try {
          const library = libraries[i];
          librariesFile.libraries.push({
            name: library.name,
            release: library.releases[library.releases.length - 1].ref.replace("refs/tags/", "")
          });
        } catch (error) {
          console.error(error);
        }
      }

      const json = JSON.stringify(librariesFile, null, "\t");
      fs.writeFile(GithubDependenciesManager.GITHUB_CACHE_FILE, json, "utf8", (exception) =&amp;gt; {
        if (exception != null) {
          console.error(GithubDependenciesManager.CONSOLE_LOGGING_KEY + " Exception : " + exception);
        }
      });
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now We Have the Updated Libraries inside Array and we want to loop on them and send messages via slack API using the Signing Key, Secret Key&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private static sendSlackMessage(configFile: ApplicationConfigFile, message: string) {
    try {
      MessagingManager.getSlackApplicationInstance(configFile.signingSecret, configFile.token).client.chat.postMessage({
        channel: configFile.channelId,
        mrkdwn: true,
        text: message,
        as_user: true,
        parse: "full",
        username: "Zilon"
      }).then((response) =&amp;gt; {
        console.log("Slack Message Response : " + response.message.text);
      }).catch((exception) =&amp;gt; {
        console.error(exception);
      });
    } catch (error) {
      console.error(error);
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this Method inside your Loop and Create your Own Message on each Library, in my case I have added all libraries and their documentation links, official websites that I need to my JSON file, and on each message, I check all of them and send them with the message&lt;/p&gt;

&lt;p&gt;In Slack Application Create a Channel and invite the app to this channel by typing /invite then pick the application and inside the code when you want to send a message on the channel you should write it to be like this (#general)&lt;/p&gt;

&lt;h3&gt;
  
  
  The Scheduled Task Result
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FYazan98%2FZilon%2Fblob%2Fmain%2Fimages%2FScreenshot%25202021-10-20%2520173745.png%3Fraw%3Dtrue" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FYazan98%2FZilon%2Fblob%2Fmain%2Fimages%2FScreenshot%25202021-10-20%2520173745.png%3Fraw%3Dtrue"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Full Example is Available on Github&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/Yazan98/Zilon" rel="noopener noreferrer"&gt;Github Repository&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>android</category>
      <category>kotlin</category>
      <category>node</category>
    </item>
  </channel>
</rss>
