<?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: Canopas Software</title>
    <description>The latest articles on DEV Community by Canopas Software (@canopassoftware).</description>
    <link>https://dev.to/canopassoftware</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%2Forganization%2Fprofile_image%2F5286%2Fe04498ad-6f74-4042-aae9-bb65d2e778af.png</url>
      <title>DEV Community: Canopas Software</title>
      <link>https://dev.to/canopassoftware</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/canopassoftware"/>
    <language>en</language>
    <item>
      <title>Golang: Struct, Interface And Dependency Injection(DI)</title>
      <dc:creator>Nandani Sharma</dc:creator>
      <pubDate>Fri, 10 Jan 2025 04:51:10 +0000</pubDate>
      <link>https://dev.to/canopassoftware/golang-struct-interface-and-dependency-injectiondi-o52</link>
      <guid>https://dev.to/canopassoftware/golang-struct-interface-and-dependency-injectiondi-o52</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;In this blog, we will explore when to use structs versus interfaces in Go. We will also look at how to leverage both for Dependency Injection (DI). &lt;/p&gt;

&lt;p&gt;To make these concepts easier to grasp, we’ll use a simple analogy of a &lt;strong&gt;Toy Box&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding with a real-world example: Toy Box
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Structs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Think of a struct as a specific toy in a toy box, like a car. &lt;/li&gt;
&lt;li&gt;The car has specific features, like its color, size, and type (e.g., sports car). &lt;/li&gt;
&lt;li&gt;In programming, a struct holds data about an object.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Interfaces
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;An interface is like a toy box that can hold any type of toy. &lt;/li&gt;
&lt;li&gt;It defines what toys can do, like roll, make noise, or light up. Any toy that can perform these actions can fit in the toy box. &lt;/li&gt;
&lt;li&gt;In programming, an interface defines a set of methods that different types(struct) can implement.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dependency Injection
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Imagine a child who plays with toys. Instead of the child only being able to play with one specific toy, you let them choose any toy from the toy box whenever they want. &lt;/li&gt;
&lt;li&gt;This is like dependency injection, where you provide a function or class with the tools (or dependencies) it needs to work, allowing for flexibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding the Basics
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Structs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition:&lt;/strong&gt; A struct is a way to define a new type with specific fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Purpose:&lt;/strong&gt; Useful for modeling data structures and encapsulating data and behavior within a single unit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Car struct {
    Model string
    Year  int
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Interfaces
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Definition:&lt;/strong&gt; An interface defines a set of methods that a type must implement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Purpose:&lt;/strong&gt; Crucial for polymorphism and decoupling components, enabling generic programming.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
type CarInterface interface {
    Start()
    Stop()
}

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

&lt;/div&gt;



&lt;p&gt;Implement &lt;code&gt;CarInterface&lt;/code&gt; using &lt;code&gt;Car&lt;/code&gt; struct,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (c *Car) Start() {
    fmt.Println("Car started")
}

func (c *Car) Stop() {
    fmt.Println("Car stopped")
}

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  When to Use Which?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Use Structs When
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need to model a specific data structure with defined fields.&lt;/li&gt;
&lt;li&gt;You want to encapsulate data and behavior within a single unit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use Interfaces When
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want to define a contract that multiple types can implement.&lt;/li&gt;
&lt;li&gt;You need to decouple components and make your code more flexible and testable.&lt;/li&gt;
&lt;li&gt;You want to leverage polymorphism to write generic code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Balancing Flexibility and Performance
&lt;/h2&gt;

&lt;p&gt;While interfaces provide flexibility, dynamic method calls can introduce overhead. &lt;/p&gt;

&lt;p&gt;Structs, on the other hand, offer performance advantages due to static type checking and direct method calls. Below are the ways to strike the balance:&lt;/p&gt;

&lt;h3&gt;
  
  
  Interface Composition
&lt;/h3&gt;

&lt;p&gt;Combine multiple interfaces to create more specific interfaces. For example, consider a file system interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can create a more specific interface &lt;code&gt;ReadWrite&lt;/code&gt;, by composing &lt;code&gt;Reader&lt;/code&gt; and &lt;code&gt;Writer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type ReadWrite interface {
    Reader
    Writer
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; This approach promotes modularity, reusability, and flexibility in your code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interface Embedding
&lt;/h3&gt;

&lt;p&gt;Embed interfaces within structs to inherit their methods. For example, consider a logging interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Logger interface {
    Log(message string)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can create a more specific interface, &lt;code&gt;ErrorLogger&lt;/code&gt;, which embeds the &lt;code&gt;Logger&lt;/code&gt; interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type ErrorLogger interface {
    Logger
    LogError(err error)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any type that implements the &lt;code&gt;ErrorLogger&lt;/code&gt; interface must also implement the &lt;code&gt;Log&lt;/code&gt; method inherited from the embedded &lt;code&gt;Logger&lt;/code&gt; interface.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type ConsoleLogger struct{}

func (cl *ConsoleLogger) Log(message string) {
    fmt.Println(message)
}

func (cl *ConsoleLogger) LogError(err error) {
    fmt.Println("Error:", err)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefit:&lt;/strong&gt; This can be used to create hierarchical relationships between interfaces, making your code more concise and expressive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency Injection
&lt;/h3&gt;

&lt;p&gt;It is a design pattern that helps decouple components and improve testability. In Go, it’s often implemented using interfaces.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example: Notification System
&lt;/h4&gt;

&lt;p&gt;In this example, we will define a notification service that can send messages through different channels. We will use DI to allow the service to work with any notification method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Define the Notifier Interface
&lt;/h2&gt;

&lt;p&gt;First, we define an interface for our notifier. This interface will specify the method for sending notifications.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type Notifier interface {
    Send(message string) error
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Implement Different Notifiers
&lt;/h2&gt;

&lt;p&gt;Next, we create two implementations of the Notifier interface: one for sending email notifications and another for sending SMS notifications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Email Notifier Implementation:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type EmailNotifier struct {
    EmailAddress string
}

func (e *EmailNotifier) Send(message string) error {
    // Simulate sending an email
    fmt.Printf("Sending email to %s: %s\n", e.EmailAddress, message)
    return nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SMS Notifier Implementation:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type SMSNotifier struct {
    PhoneNumber string
}

func (s *SMSNotifier) Send(message string) error {
    // Simulate sending an SMS
    fmt.Printf("Sending SMS to %s: %s\n", s.PhoneNumber, message)
    return nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Create the Notification Service
&lt;/h2&gt;

&lt;p&gt;Now, we create a &lt;code&gt;NotificationService&lt;/code&gt; that will use the &lt;code&gt;Notifier&lt;/code&gt; interface. This service will be responsible for sending notifications.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type NotificationService struct {
    notifier Notifier
}

func NewNotificationService(n Notifier) *NotificationService {
    return &amp;amp;NotificationService{notifier: n}
}

func (ns *NotificationService) Notify(message string) error {
    return ns.notifier.Send(message)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Use Dependency Injection in the Main Function&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the main function, we will create instances of the notifiers and inject them into the &lt;code&gt;NotificationService&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func main() {
    // Create an email notifier
    emailNotifier := &amp;amp;EmailNotifier{EmailAddress: "john@example.com"}
    emailService := NewNotificationService(emailNotifier)
    emailService.Notify("Hello via Email!")

    // Create an SMS notifier
    smsNotifier := &amp;amp;SMSNotifier{PhoneNumber: "123-456-7890"}
    smsService := NewNotificationService(smsNotifier)
    smsService.Notify("Hello via SMS!")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Benefits of This Approach
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decoupling:&lt;/strong&gt; The &lt;code&gt;NotificationService&lt;/code&gt; does not depend on specific implementations of &lt;code&gt;notifiers&lt;/code&gt;. It only relies on the Notifier interface, making it easy to add new notification methods in the future.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability:&lt;/strong&gt; You can easily create mock implementations of the Notifier interface for unit testing of the &lt;code&gt;NotificationService&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility:&lt;/strong&gt; If you want to add a new notification method (like push notifications), you can create a new struct that implements the &lt;code&gt;Notifier&lt;/code&gt; interface without changing the &lt;code&gt;NotificationService&lt;/code&gt; code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding when to use structs versus interfaces is essential for writing clean, maintainable, and testable code in Go. &lt;/p&gt;

&lt;p&gt;By leveraging both concepts along with Dependency Injection, we can create flexible and robust applications.&lt;/p&gt;

&lt;p&gt;To Read the full version of this blog, Visit our &lt;strong&gt;&lt;a href="//canopas.com/golang-struct-interface-and-dependency-injection"&gt;Canopas Blog&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;If you like what you read, be sure to hit 💖 button! — as a writer it means the world!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.&lt;/p&gt;

&lt;p&gt;Happy coding!👋&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>go</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Google's Geocoding APIs: Frontend and Backend Implementation</title>
      <dc:creator>Nandani Sharma</dc:creator>
      <pubDate>Wed, 08 Jan 2025 08:52:56 +0000</pubDate>
      <link>https://dev.to/canopassoftware/googles-geocoding-apis-frontend-and-backend-implementation-4oo6</link>
      <guid>https://dev.to/canopassoftware/googles-geocoding-apis-frontend-and-backend-implementation-4oo6</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Nowadays, we can’t imagine our life without a Location Map. We are exploring so many things using maps. From finding the fastest route to a destination, discovering nearby restaurants, or even tracking deliveries in real time, these services rely on location and mapping APIs.&lt;/p&gt;

&lt;p&gt;Whenever we are building any application, there may be requirements for working with user location like finding addresses or integrating maps. Among the many solutions available, Google Maps APIs are the most powerful and widely used tools for developers.&lt;/p&gt;

&lt;p&gt;In this blog, we will explore Google’s various location-based APIs, with a major focus on the &lt;strong&gt;Geocoding API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You’ll learn:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The different APIs offered by Google Maps and how they work.&lt;/li&gt;
&lt;li&gt;How to use the Geocoding API on a web server for converting addresses to coordinates (and vice versa).&lt;/li&gt;
&lt;li&gt;How to build a Typescript-powered frontend to display the results interactively.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s navigate through maps!!!&lt;/p&gt;

&lt;h2&gt;
  
  
  Type of Google Maps API
&lt;/h2&gt;

&lt;p&gt;When I started exploring Google Maps, I was overwhelmed by its huge set of APIs. It was difficult for me to identify the right API for my use case. I explored all the APIs and here is a breakdown of it.&lt;/p&gt;

&lt;p&gt;Google Maps APIs help developers to integrate advanced mapping and location-based features into their applications. These APIs allow a range of functionalities, from embedding interactive maps to fetching detailed location data, calculating distances, and managing real-time routing.&lt;/p&gt;

&lt;p&gt;Below, we’ll break down some of these most useful APIs, explaining their purpose and how they work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mapping APIs
&lt;/h3&gt;

&lt;p&gt;These APIs can be used to create and display static or interactive maps as per the needs of the application.&lt;/p&gt;

&lt;h4&gt;
  
  
  Google Maps API
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;This API allows &lt;strong&gt;embedding interactive, customizable maps&lt;/strong&gt; directly into applications. &lt;/li&gt;
&lt;li&gt;Users can pan, zoom, and switch between map types (satellite, terrain, hybrid, etc.). &lt;/li&gt;
&lt;li&gt;Developers can add markers, polygons, and custom overlays to enrich the map.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use cases:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Real-time location tracking applications.&lt;/li&gt;
&lt;li&gt;Interactive trip planners.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Static Maps API
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;When any application needs &lt;strong&gt;static, non-interactive maps&lt;/strong&gt;, this API will be useful. &lt;/li&gt;
&lt;li&gt;API is ideal for embedding maps in s*&lt;em&gt;tatic websites, reports, and emails&lt;/em&gt;* and is easy to use with URL-based parameters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Cases:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Visualising data points in reports.&lt;/li&gt;
&lt;li&gt;Adding maps to non-dynamic web pages.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Street View API
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It enables developers to &lt;strong&gt;display 360° panoramic views of streets and landmarks.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;This API allows virtual exploration of locations through street-level imagery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Cases:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Real estate applications for property viewing.&lt;/li&gt;
&lt;li&gt;Travel &amp;amp; tourism apps&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Places APIs
&lt;/h3&gt;

&lt;p&gt;Places APIs enable applications to fetch detailed information about points of interest (POIs), such as landmarks, businesses, or public facilities.&lt;/p&gt;

&lt;h4&gt;
  
  
  Geocoding &amp;amp; Reverse Geocoding API
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;These APIs are essential for translating between human-readable addresses and geographic coordinates like latitude and longitude (e.g., 37.422, -122.084).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Geocoding(address -&amp;gt; coordinates)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One of the use cases for this API is a food delivery app, which converts user-inputted addresses to Geo coordinates and loads Google Maps based on them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reverse geocoding (coordinates -&amp;gt; address)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This API can be used in tracking applications like Vehicle or user tracking. It will display the nearest address of the given coordinates.&lt;/p&gt;

&lt;h4&gt;
  
  
  Autocomplete API
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;This API will be useful for search-based location applications like form-filling.&lt;/li&gt;
&lt;li&gt;It suggests relevant location predictions as users type into a search field.&lt;/li&gt;
&lt;li&gt;Use Cases:

&lt;ul&gt;
&lt;li&gt;Search bars in maps or location selection forms.&lt;/li&gt;
&lt;li&gt;Reducing user efforts and errors in typing addresses.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Time Zones API
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;It provides time zone information for any given location.&lt;/li&gt;
&lt;li&gt;It takes Geographic coordinates and a timestamp as input and returns the name of the time zone, UTC offset, and Daylight Saving Time (DST) information.&lt;/li&gt;
&lt;li&gt;Use Cases:

&lt;ul&gt;
&lt;li&gt;Applications displaying local times for events or activities.&lt;/li&gt;
&lt;li&gt;Travel apps adapt schedules to different time zones.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Routes APIs
&lt;/h3&gt;

&lt;p&gt;Routes APIs are designed to provide navigation capabilities and distance calculations.&lt;/p&gt;

&lt;h4&gt;
  
  
  Directions API
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;This is widely used in Google Maps. &lt;/li&gt;
&lt;li&gt;It offers detailed routes between two or more locations, including step-by-step navigation.&lt;/li&gt;
&lt;li&gt;You just need to add start and end locations and it outputs directions, time, and distance with various travel modes (driving, walking, cycling, etc.).&lt;/li&gt;
&lt;li&gt;Use Cases:

&lt;ul&gt;
&lt;li&gt;Ride-hailing services optimising routes for drivers.&lt;/li&gt;
&lt;li&gt;Apps provide directions for users navigating cities like Google Maps.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Distance Matrix API
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;This API calculates travel distance and time between multiple origins and destinations.&lt;/li&gt;
&lt;li&gt;Use Cases:

&lt;ul&gt;
&lt;li&gt;Delivery logistics calculate the most efficient routes.&lt;/li&gt;
&lt;li&gt;Multi-location trip planning.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  Enable Google Maps services
&lt;/h2&gt;

&lt;p&gt;To use the above services(APIs), we need an API key generated from the Google Cloud console. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: Before starting with any of the google map services, I recommend you to go through its security and &lt;a href="https://developers.google.com/maps/documentation/javascript/usage-and-billing" rel="noopener noreferrer"&gt;billing&lt;/a&gt; documents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Below are the prerequisites to get and restrict API keys,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/maps/documentation/elevation/cloud-setup" rel="noopener noreferrer"&gt;Setup Google Cloud project&lt;/a&gt; &amp;amp; Enable billing (Require to use Google Maps API)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/maps/documentation/elevation/cloud-setup#view-enabled-apis" rel="noopener noreferrer"&gt;Go to Google Onboard and Enable Required API&lt;/a&gt; (For this blog, it will be Geocoding API)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/maps/documentation/elevation/get-api-key#creating-api-keys" rel="noopener noreferrer"&gt;Generate&lt;/a&gt; &amp;amp; &lt;a href="https://developers.google.com/maps/documentation/elevation/get-api-key#restrict_key" rel="noopener noreferrer"&gt;Restrict&lt;/a&gt; an API key as needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  API key restrictions
&lt;/h3&gt;

&lt;p&gt;This will be helpful to secure the API key and prevent misuse of it. which eventually prevents unwanted high billing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For running API in the Browser(Frontend) environment, use Websites restriction.&lt;/li&gt;
&lt;li&gt;For running API in the backend environment, use IP addresses restriction.&lt;/li&gt;
&lt;li&gt;Allow only the required API for the key.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Implementing Geocoding and Reverse Geocoding
&lt;/h2&gt;

&lt;p&gt;We’ll now explore how to use the Geocoding API in both Frontend and Backend(Web server API) environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend Implementation
&lt;/h3&gt;

&lt;p&gt;Using Geocoding API directly in frontend, there are some drawbacks like API key exposure and performance issues. However, we can address these issues by adding API key restrictions and implementing caching.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an index.html file and add a script in the head,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset="UTF-8"&amp;gt;
  &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;gt;
  &amp;lt;title&amp;gt;Google Places API&amp;lt;/title&amp;gt;
  &amp;lt;script src="https://maps.googleapis.com/maps/api/js?key=GOOGLE_MAPS_API_KEY&amp;amp;libraries=places"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By adding this script, the browser will able to access Google objects from this script using &lt;code&gt;window.google&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the Geocoding logic in &lt;code&gt;geocoding.ts&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  GeoCoding
&lt;/h4&gt;

&lt;p&gt;In this, we will pass a human-readable address and get the coordinates of that location.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function fetchCoordinates() {
    new window.google.maps.Geocoder().geocode(
        { address: '1600 Amphitheatre Parkway, Mountain View, CA' },
        (results: any, status: string) =&amp;gt; {
          if (status === "OK" &amp;amp;&amp;amp; results &amp;amp;&amp;amp; results[0]) {
            console.log(
              'Latitude:', results[0].geometry.location.lat()
            );
            console.log(
              'Longitude:', results[0].geometry.location.lng()
            );
          } else {
            console.error(
              "Error fetching coordinates from geocoding: " + status
            );
          }
        }
      );
    }
 }

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

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: We will use only &lt;strong&gt;geometry&lt;/strong&gt;, review full response &lt;u&gt;&lt;a href="https://developers.google.com/maps/documentation/geocoding/requests-geocoding#GeocodingResponses" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/u&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Build a typescript file and add a geocoding.js file inside index.html,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  &amp;lt;script type="module" src="geocoding.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run index.html, the output will be,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Latitude: 37.4225018
Longitude: -122.0847402
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Reverse GeoCoding
&lt;/h4&gt;

&lt;p&gt;In this, we will pass the coordinates and get the human-readable address.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function fetchLocation() {

    const coordinates = {
      lat: 37.422,
      lng: -122.084,
    };

    new window.google.maps.Geocoder().geocode(
        { location: coordinates },
        (results: any, status: string) =&amp;gt; {
          if (status === "OK" &amp;amp;&amp;amp; results &amp;amp;&amp;amp; results[0]) {
            console.log(
              "Address: " + results[0].address_components
            );
          } else {
            console.error(
              "Error fetching addressComponents from reverse geocoding: " + status
            );
          }
        }
      );
    }
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: We will use only address_components, review full response &lt;u&gt;&lt;a href="https://developers.google.com/maps/documentation/geocoding/requests-reverse-geocoding#reverse-example" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/u&gt;. &lt;br&gt;
Also, You can find all the fields of address_components &lt;u&gt;&lt;a href="https://developers.google.com/maps/documentation/javascript/geocoding#GeocodingAddressTypes" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/u&gt; and can use the required field for your use case.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Extract human-readable address from address_components,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const extractLocation = (addressComponents: any[]) =&amp;gt; {

    const findAddressType = (types, addressComponents) =&amp;gt; {
        const found = addressComponents.find((c) =&amp;gt;
          types.some((t) =&amp;gt; c.types.includes(t)),
        );
        return found?.longText ?? found?.long_name ?? "";
    };

    return {
        area: findAddressType(['sublocality_level_1', 'locality'], addressComponents),
        city: findAddressType(['locality'], addressComponents),
        state: findAddressType(['administrative_area_level_1'], addressComponents),
        pincode: findAddressType(['postal_code'], addressComponents),
    };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Print the address as below,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.error(
   "Address: " + JSON.stringify(extractLocation(results[0].address_components))
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Build a typescript and run index.html. The output will be,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Address: {"area":"Mountain View","city":"Mountain View","state":"California","pincode":"94043"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;To explore the Geocoding and Reverse Geocoding in &lt;strong&gt;Backend(Web server API)&lt;/strong&gt; environments, Visit our full guide at &lt;strong&gt;&lt;a href="https://canopas.com/google-s-geocoding-apis-frontend-and-backend-implementation" rel="noopener noreferrer"&gt;Canopas Blog.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;If you like what you read, be sure to hit 💖 button! — as a writer it means the world!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.&lt;/p&gt;

&lt;p&gt;Happy coding!👋&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>node</category>
      <category>programming</category>
    </item>
    <item>
      <title>Exploring Cupertino and Material Updates in Flutter 3.27.0</title>
      <dc:creator>Nandani Sharma</dc:creator>
      <pubDate>Fri, 27 Dec 2024 10:45:01 +0000</pubDate>
      <link>https://dev.to/canopassoftware/exploring-cupertino-and-material-updates-in-flutter-3270-2433</link>
      <guid>https://dev.to/canopassoftware/exploring-cupertino-and-material-updates-in-flutter-3270-2433</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Flutter 3.27.0 arrived with a bang, bringing a wave of improvements to both performance and developer experience. This release shows how Flutter is always getting better and helping developers make great apps.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore the key updates of the Cupertino and Material widgets in Flutter 3.27.0&lt;/p&gt;

&lt;p&gt;Ready? Let’s dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  SliverFloatingHeader
&lt;/h2&gt;

&lt;p&gt;Just like Google Photos, The &lt;code&gt;SliverFloatingHeader&lt;/code&gt; is a widget designed to create headers that appear when the user scrolls forward and gracefully disappear when scrolling in the opposite direction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CustomScrollView(
  slivers: [
    SliverFloatingHeader(
        child: Container(
      padding: const EdgeInsets.all(10),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(10),
        color: Colors.orange,
      ),
      child: const Text(
        "Sliver Floating Header like Google photos",
        style: TextStyle(
          fontSize: 20,
          fontWeight: FontWeight.bold,
          color: Colors.black,
        ),
      ),
    )),
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (context, index) {
          return ListTile(
            title: Text("Item $index"),
            tileColor: index.isEven? Colors.grey.shade300 : Colors.grey.shade100,
          );
        },
        childCount: 100,
      ),
    ),
  ],
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2F03apfhaipph2k2f7bn20.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F03apfhaipph2k2f7bn20.gif" alt="SliverFloatingHeader" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tubular Figures
&lt;/h2&gt;

&lt;p&gt;Have you ever faced UI misalignment when displaying monospaced text like &lt;code&gt;DateTime&lt;/code&gt;, such as &lt;code&gt;01:00 PM&lt;/code&gt; or &lt;code&gt;02:30 PM&lt;/code&gt;? I encountered this issue while building the Canbook app, where I needed to display time slots. Initially, I tried calculating the width based on the thickest character (&lt;code&gt;0&lt;/code&gt;) to fix the alignment.&lt;/p&gt;

&lt;p&gt;How can we improve the UI for rendering monospaced text like &lt;code&gt;DateTime&lt;/code&gt; and &lt;code&gt;time&lt;/code&gt;, and maintain alignment and consistency for dynamic text updates?&lt;/p&gt;

&lt;p&gt;In the old UI implementation, digits had variable widths due to the default font settings. This resulted in inconsistent alignment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
SingleChildScrollView(
  child: Wrap(
    alignment: WrapAlignment.start,
    crossAxisAlignment: WrapCrossAlignment.center,
    spacing: 8,
    runSpacing: 8,
    children: List.generate(30, (index) {
      final time = DateTime.now().add(Duration(minutes: (index) * 15));
      return Container(
        padding: const EdgeInsets.all(8),
        decoration: BoxDecoration(
          border: Border.all(
            color: Colors.white,
          ),
          borderRadius: BorderRadius.circular(8),
        ),
        child: Text(
          DateFormat('hh:mm a').format(time),
          style: const TextStyle(
              fontFeatures: [FontFeature.tabularFigures()],
              color: Colors.white),
        ),
      );
    }),
  ),
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2F0ye8j2hh7zj3gxzg4g62.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0ye8j2hh7zj3gxzg4g62.png" alt="Inconsistent Alignment" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FontFeature.tabularFigures()&lt;/code&gt; feature ensures that each character occupies the same width.&lt;/p&gt;

&lt;p&gt;This feature enables the use of tabular figures for fonts with both proportional (varying width) and tabular figures. Tabular figures are monospaced, meaning all characters have the same width, making them ideal for aligning text in tables, lists, or dynamic UI elements like time slots or numeric data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Text(
  DateFormat('hh:mm a').format(time),
  style: const TextStyle(
      fontFeatures: [FontFeature.tabularFigures()],
      color: Colors.white),
),
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2Fnvsh0pp830fqv8ln0xmv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnvsh0pp830fqv8ln0xmv.png" alt="FontFeatures with tabularFigures" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CupertinoSliverNavigationBar
&lt;/h2&gt;

&lt;p&gt;With the latest update, both &lt;code&gt;CupertinoNavigationBar&lt;/code&gt; and &lt;code&gt;CupertinoSliverNavigationBar&lt;/code&gt; now feature transparent backgrounds until content scrolls underneath them.&lt;/p&gt;

&lt;p&gt;This enables,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The navigation bar to match the background color in its expanded state.&lt;/li&gt;
&lt;li&gt;A customizable color in its collapsed state.&lt;/li&gt;
&lt;li&gt;Smooth transitions between these colors as the user scrolls.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CustomScrollView(
  slivers: [
    const CupertinoSliverNavigationBar(
     largeTitle : Text(
        'Cupertino Transparent Navigation Bar',
        style: TextStyle(color: Colors.white, fontSize: 20),
      ),
      backgroundColor: Colors.transparent,
    ),
    SliverList(
      delegate: SliverChildBuilderDelegate(
        (context, index) {
          return ListTile(
            title: Text("Item $index"),
          );
        },
        childCount: 100,
      ),
    ),
  ],
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2Fbmlshrzx9cj9i0rwbijc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbmlshrzx9cj9i0rwbijc.gif" alt="CupertinoSliverNavigationBar" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Repeat Animation
&lt;/h2&gt;

&lt;p&gt;Flutter’s &lt;code&gt;repeat&lt;/code&gt; method allows animations to run infinitely. But what if you want the animation to run for a specific number of times? &lt;/p&gt;

&lt;p&gt;Now, with the addition of the &lt;code&gt;count&lt;/code&gt; parameter, the &lt;code&gt;repeat&lt;/code&gt; method can restart the animation and perform a set number of iterations before stopping.&lt;/p&gt;

&lt;p&gt;If no value is provided for &lt;code&gt;count&lt;/code&gt;, the animation will repeat indefinitely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@override
void initState() {
  super.initState();
  controller = AnimationController(
    duration: const Duration(seconds: 2),
    vsync: this,
  );
  animation = Tween&amp;lt;double&amp;gt;(begin: 100, end: 300).animate(controller)
    ..addListener(() {
      setState(() {});
    });
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Container(
          width: animation.value,
          height: animation.value,
          color: Colors.yellow),
    ),
    floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
    floatingActionButton: FloatingActionButton.extended(
      onPressed: () {
        controller.repeat(count: 2);
      },
      label: Text("Start Animation"),
    ),
  );
}

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2Flykneks4icsfltcl9qoo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flykneks4icsfltcl9qoo.gif" alt="Repeat Animation with count" width="295" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Row and Column Spacing
&lt;/h2&gt;

&lt;p&gt;With the release of Flutter 3.27.0, &lt;code&gt;Row&lt;/code&gt; and &lt;code&gt;Column&lt;/code&gt; widgets now support the spacing parameter, eliminating the need for manual SizedBox or Padding to add &lt;code&gt;spacing&lt;/code&gt; between child widgets.&lt;/p&gt;

&lt;p&gt;Now, with Flutter 3.27, you can simplify the layout by directly using the &lt;code&gt;spacing&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;SizedBox&lt;/code&gt; for spacing between children,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@override
Widget build(BuildContext context) {
  return Column(
    children: [
      _item(),
      const SizedBox(height: 16),
      _item(),
      const SizedBox(height: 16),
      _item(),
      const SizedBox(height: 16),
      _item(),
      const SizedBox(height: 16),
      _item(),
    ],
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Updates,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@override
  Widget build(BuildContext context) {
    return Column(
      spacing: 16,
      children: [
        _item(),
        _item(),
        _item(),
        _item(),
      ],
    );
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tap to scroll to the item in CupertinoPicker
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CupertinoPicker&lt;/code&gt; now supports tapping on an item to automatically scroll to it, to navigate directly to the selected item.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fyw2fj89sjks8s6r4fah7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fyw2fj89sjks8s6r4fah7.gif" alt="CupertinoPicker with AutoScroll" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Refresh indicator
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Refresh Indicator with No Spinner
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;RefreshIndicator.noSpinner&lt;/code&gt; widget to create a refresh indicator without the default spinner. This allows you to handle the refresh action with custom UI elements while still using the drag-to-refresh functionality.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stack(
  children: [
    RefreshIndicator.noSpinner(
      onRefresh: () async {
        isRefreshing = true;
        setState(() {});
        await Future.delayed(const Duration(seconds: 2));
        isRefreshing = false;
        setState(() {});
      }, 
      child: ListView()
    ),
    if (isRefreshing)
      Align(
        child: CircularProgressIndicator(
          color: Colors.orange,
        ),
      ),
  ],
),
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2Fixj6v5hq83zq0h10y7fo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fixj6v5hq83zq0h10y7fo.gif" alt="RefreshIndicator without Spinner" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Elevation
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;elevation&lt;/code&gt; property determines the shadow depth of the &lt;code&gt;RefreshIndicator&lt;/code&gt;. The default value is &lt;code&gt;2.0&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RefreshIndicator(
        elevation: 10,
        backgroundColor: Colors.orange,
        onRefresh: () async {
          await Future.delayed(const Duration(seconds: 2));
        }, // Callback function for refresh action
        child: ListView()
      ),
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2Fg8xbqxvpo4jy13a7ztrx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fg8xbqxvpo4jy13a7ztrx.gif" alt="Refresh Indicator With 0 Elevation" width="295" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fev2np25qlyx7k5yokztw.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fev2np25qlyx7k5yokztw.gif" alt="Refresh Indicator With 10 Elevation" width="295" height="200"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;With enhancements in both design and functionality, this release makes building beautiful, cross-platform apps easier than ever.      &lt;/p&gt;

&lt;p&gt;So, Curious to explore the full breakdown of these updates…?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Head over to our full blog at &lt;strong&gt;&lt;a href="https://canopas.com/exploring-cupertino-and-material-updates-in-flutter-3-27-cb4c76e222e1" rel="noopener noreferrer"&gt;Canopas&lt;/a&gt;&lt;/strong&gt; to explore all the new key updates in Flutter 3.27.0! 🚀&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;If you like what you read, be sure to hit 💖 button! — as a writer it means the world!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.&lt;/p&gt;

&lt;p&gt;Happy coding!👋&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>programming</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Firebase Authentication: Google, Apple, and Phone Login to iOS App</title>
      <dc:creator>Nandani Sharma</dc:creator>
      <pubDate>Thu, 19 Dec 2024 11:17:39 +0000</pubDate>
      <link>https://dev.to/canopassoftware/firebase-authentication-google-apple-and-phone-login-to-ios-app-581l</link>
      <guid>https://dev.to/canopassoftware/firebase-authentication-google-apple-and-phone-login-to-ios-app-581l</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Integrating Firebase Authentication into your SwiftUI app offers a secure and efficient way to manage user sign-ins. With Firebase, you can authenticate users using various login methods, like Google, Apple, Email, phone number, and many more authentications.&lt;/p&gt;

&lt;p&gt;This integration is crucial for apps that require user identification and provides a smooth and unified login experience across platforms.&lt;/p&gt;

&lt;p&gt;In this blog, we’ll walk you through setting up Firebase Authentication in a SwiftUI app, focusing on three key authentication providers: Google, Apple, and Phone number login.&lt;/p&gt;

&lt;p&gt;We aim to simplify the setup and implementation process to help you build a secure, user-friendly app.&lt;/p&gt;

&lt;p&gt;If you’d prefer to skip the detailed explanation in this blog, the complete source code is available on this &lt;strong&gt;&lt;a href="https://github.com/cp-amisha-i/FirebaseChatApp" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firebase Project Setup
&lt;/h2&gt;

&lt;p&gt;Before integrating Firebase Authentication into your app, you need to set up a Firebase project.&lt;/p&gt;

&lt;p&gt;Follow these steps to set up Firebase:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Create an Xcode Project
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Start by creating an initial Xcode project for your app. This project will serve as the base for integrating Firebase Authentication.&lt;/li&gt;
&lt;li&gt;Make sure you set a &lt;strong&gt;proper bundle identifier&lt;/strong&gt;. For instance, if you are building a chat app, your bundle identifier could be &lt;code&gt;com.example.FirebaseChatApp&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Create a Firebase Project
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Next, navigate to the &lt;strong&gt;&lt;a href="https://console.firebase.google.com/" rel="noopener noreferrer"&gt;Firebase Console&lt;/a&gt;&lt;/strong&gt; and create a new project.&lt;/li&gt;
&lt;li&gt;Give your project a name (e.g., &lt;code&gt;FirebaseChatApp&lt;/code&gt;), and configure any settings based on your needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable Google Analytics&lt;/strong&gt; is optional and can be done if you plan to collect app usage data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. Add Your iOS App to Firebase
&lt;/h4&gt;

&lt;p&gt;After setting up the Firebase project, you must connect your iOS app.&lt;/p&gt;

&lt;p&gt;To do this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the Firebase Console, go to Project Overview and click Add App.&lt;/li&gt;
&lt;li&gt;Select iOS and enter your app’s bundle identifier.&lt;/li&gt;
&lt;li&gt;Download the G*&lt;em&gt;oogleService-Info.plist&lt;/em&gt;* file and drag it into your Xcode project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4. Install Firebase SDK Using Swift Package Manager or CocoaPods
&lt;/h4&gt;

&lt;p&gt;You can install Firebase SDK either using Swift Package Manager (SPM) or CocoaPods.&lt;/p&gt;

&lt;p&gt;For Swift Package Manager, follow these steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In Xcode, go to &lt;strong&gt;File &amp;gt; Add Packages.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enter the Firebase SDK repository URL: &lt;a href="https://github.com/firebase/firebase-ios-sdk" rel="noopener noreferrer"&gt;https://github.com/firebase/firebase-ios-sdk&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For CocoaPods, add the following dependencies to your &lt;code&gt;Podfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pod 'GoogleSignIn'    # For Google login
pod 'FirebaseAuth'    # For Firebase Authentication
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;pod install&lt;/code&gt; in the terminal to install the dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize the Firebase app
&lt;/h2&gt;

&lt;p&gt;Once the Firebase SDK is installed, initialize it in your app to enable Firebase services, such as Authentication.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a FirebaseProvider Class
&lt;/h3&gt;

&lt;p&gt;To make the initialization and authentication process smoother, create a FirebaseProvider class. This class will handle Firebase setup and provide easy access to Firebase’s Auth API.&lt;/p&gt;

&lt;p&gt;Here’s an implementation of the FirebaseProvider class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import FirebaseCore
import FirebaseAuth

public class FirebaseProvider {

    public static var auth: Auth = .auth()

    static public func configureFirebase() {
        FirebaseApp.configure() // Initializes Firebase with the default settings.
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Configure Firebase in the AppDelegate&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Add the AppDelegate class to initialize Firebase when the app launches.&lt;/p&gt;

&lt;p&gt;This class handles app-level events like application launch, backgrounding, and foregrounding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -&amp;gt; Bool {
        FirebaseProvider.configureFirebase()
        return true
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Design Login Screen
&lt;/h2&gt;

&lt;p&gt;Let’s start with designing a simple login screen that provides buttons for the three authentication providers: Google, Apple, and Phone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct LoginView: View {

    @State var viewModel: LoginViewModel

    var body: some View {
        VStack {
            LoginOptionsView(showGoogleLoading: viewModel.showGoogleLoading, showAppleLoading: viewModel.showAppleLoading,
                             onGoogleLoginClick: viewModel.onGoogleLoginClick, onAppleLoginClick: viewModel.onAppleLoginClick,
                             onPhoneLoginClick: viewModel.onPhoneLoginClick)
        }
        .background(.surface)
    }
}

private struct LoginOptionsView: View {

    let showGoogleLoading: Bool
    let showAppleLoading: Bool

    let onGoogleLoginClick: () -&amp;gt; Void
    let onAppleLoginClick: () -&amp;gt; Void
    let onPhoneLoginClick: () -&amp;gt; Void

    var body: some View {
        VStack(spacing: 8) {
            LoginOptionsButtonView(image: .googleIcon, buttonName: "Sign in with Google", showLoader: showGoogleLoading,
                                   onClick: onGoogleLoginClick)
            LoginOptionsButtonView(systemImage: ("apple.logo", .primaryText, (14, 16)), buttonName: "Sign in with Apple",
                                   showLoader: showAppleLoading, onClick: onAppleLoginClick)
            LoginOptionsButtonView(systemImage: ("phone.fill", .white, (12, 12)),
                                   buttonName: "Sign in with Phone Number", bgColor: .appPrimary,
                                   buttonTextColor: .white, showLoader: false, onClick: onPhoneLoginClick)
        }
        .padding(.horizontal, 16)
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
    }
}

private struct LoginOptionsButtonView: View {

    let image: ImageResource?
    let systemImage: (name: String, color: Color, size: (width: CGFloat, height: CGFloat))?
    let buttonName: String
    var bgColor: Color
    var buttonTextColor: Color
    let showLoader: Bool
    let onClick: () -&amp;gt; Void

    init(image: ImageResource? = nil,
         systemImage: (name: String, color: Color, size: (width: CGFloat, height: CGFloat))? = nil,
         buttonName: String, bgColor: Color = .container, buttonTextColor: Color = .primaryDark,
         showLoader: Bool, onClick: @escaping () -&amp;gt; Void) {
        self.image = image
        self.systemImage = systemImage
        self.buttonName = buttonName
        self.bgColor = bgColor
        self.buttonTextColor = buttonTextColor
        self.showLoader = showLoader
        self.onClick = onClick
    }

    public var body: some View {
        Button {
            onClick()
        } label: {
            HStack(alignment: .center, spacing: 12) {
                if showLoader {
                    ImageLoaderView(tintColor: .appPrimary)
                }

                if let image {
                    Image(image)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: 18, height: 18)
                } else if let systemImage {
                    Image(systemName: systemImage.name)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .frame(width: systemImage.size.width, height: systemImage.size.height)
                        .foregroundStyle(systemImage.color)
                }

                Text(buttonName)
                    .lineLimit(1)
                    .foregroundStyle(buttonTextColor)
                    .frame(height: 44)
                    .minimumScaleFactor(0.5)
            }
            .padding(.horizontal, 16)
            .frame(maxWidth: .infinity, alignment: .center)
            .background(bgColor)
            .clipShape(Capsule())
        }
        .buttonStyle(.scale)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, I’m not mentioning all the color codes, you can pick them from this &lt;strong&gt;&lt;a href="https://github.com/cp-amisha-i/FirebaseChatApp/tree/main/FirebaseChatApp/Resources/Assets.xcassets/Colors" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Main App Entry Point
&lt;/h2&gt;

&lt;p&gt;In the &lt;a class="mentioned-user" href="https://dev.to/main"&gt;@main&lt;/a&gt; entry point of your app, you will use the LoginView:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@main
struct FirebaseChatAppApp: App {

    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate

    init() {
        Injector.shared.initInjector()
    }

    var body: some Scene {
        WindowGroup {
            LoginView()
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;You can get the full code of the Injector class from the &lt;a href="https://github.com/cp-amisha-i/FirebaseChatApp/blob/main/FirebaseChatApp/DI/Injector.swift" rel="noopener noreferrer"&gt;GitHub Repo.&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, Let’s add the ViewModel class.&lt;/p&gt;

&lt;p&gt;We will use the @Observable macro for the ViewModel class instead of the @ObservableObject macro in this project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Observable
class LoginViewModel: BaseViewModel {

    private var preference: AppPreference

    private(set) var showGoogleLoading = false
    private(set) var showAppleLoading = false

    private var currentNonce: String = ""
    private var appleSignInDelegates: SignInWithAppleDelegates?
}

func onGoogleLoginClick() { }

func onAppleLoginClick() { }

func onPhoneLoginClick() { }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For now, we are going to add just the method names, we’ll add further implementation later with each provider implementation.&lt;/p&gt;

&lt;p&gt;Let’s run the app,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F16iddf8ilnx9vov06x2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F16iddf8ilnx9vov06x2m.png" alt="Login screen view" width="300" height="652"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Firebase Authentication
&lt;/h2&gt;

&lt;p&gt;Firebase Authentication allows us to integrate multiple sign-in methods into our app.&lt;/p&gt;

&lt;p&gt;Before implementing the login functionality for each provider, ensure that the appropriate authentication methods are enabled in the Firebase Console.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can also get a basic overview of Firebase Authentication from the official &lt;strong&gt;&lt;a href="https://firebase.google.com/docs/auth/ios/start" rel="noopener noreferrer"&gt;Firebase document&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Enable Authentication Providers
&lt;/h3&gt;

&lt;p&gt;Navigate to Firebase Console &amp;gt; Authentication &amp;gt; Sign-in Method.&lt;/p&gt;

&lt;p&gt;Initially, you’ll have all the providers listed below for your Firebase project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fap6b8f64rq4jh9jj3aki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fap6b8f64rq4jh9jj3aki.png" alt="All Authentication Providers" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to enable the following providers for our current implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google:&lt;/strong&gt; This allows users to sign in with their Google accounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apple:&lt;/strong&gt; This enables Apple Sign-In, mandatory for apps on iOS devices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phone:&lt;/strong&gt; This allows users to sign in using their phone numbers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Google Login Integration
&lt;/h2&gt;

&lt;p&gt;Here’s a step-by-step explanation of integrating Google Login into your app using Firebase Authentication.&lt;/p&gt;

&lt;p&gt;The process consists of enabling Google login in Firebase, configuring URL schemes in Xcode, and implementing the login logic in your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable Google Login in Firebase
&lt;/h3&gt;

&lt;p&gt;In the Firebase Console, navigate to Authentication &amp;gt; Sign-in Method and enable the Google sign-in provider.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Enable Google Login in Firebase Console (if you haven’t):
&lt;/h4&gt;

&lt;p&gt;Navigate to Authentication &amp;gt; Sign-in Method and enable the Google sign-in provider.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Download &lt;code&gt;GoogleService-Info.plist&lt;/code&gt;:
&lt;/h4&gt;

&lt;p&gt;After enabling it, download the updated &lt;code&gt;GoogleService-Info.plist&lt;/code&gt; file.&lt;br&gt;
Add this file to your Xcode project, and ensure it is included in your app’s target.&lt;/p&gt;
&lt;h4&gt;
  
  
  3. Check the &lt;code&gt;clientID&lt;/code&gt;:
&lt;/h4&gt;

&lt;p&gt;Verify that the &lt;code&gt;clientID&lt;/code&gt; is present in the &lt;code&gt;GoogleService-Info.plist&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to refer to the official doc for all basic setup for Google login, refer to this &lt;strong&gt;&lt;a href="https://firebase.google.com/docs/auth/ios/google-signin" rel="noopener noreferrer"&gt;Firebase Doc&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Configure URL Schemes in Xcode
&lt;/h3&gt;

&lt;p&gt;In Xcode, &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;select your app target, go to the Info tab, and locate the URL Types section.&lt;/li&gt;
&lt;li&gt;Under URL Types, add a new entry and paste the &lt;code&gt;REVERSED_CLIENT_ID&lt;/code&gt; from the &lt;code&gt;GoogleService-Info.plist&lt;/code&gt; file into the URL Schemes field.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fzf7m4fctph06pypn7hsb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fzf7m4fctph06pypn7hsb.png" alt="URL Schemes" width="800" height="236"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Implement Google Login
&lt;/h3&gt;

&lt;p&gt;Now, let’s implement the Google Login functionality in your app.&lt;/p&gt;

&lt;p&gt;Import the required frameworks in your &lt;code&gt;LoginViewModel&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import GoogleSignIn
import FirebaseCore
import FirebaseAuth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, implement the Google login functionality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func onGoogleLoginClick() {

    // Ensure the Firebase client ID is available; otherwise, return early.
    guard let clientID = FirebaseApp.app()?.options.clientID else { return }

    // Create a Google Sign-In configuration object with the Firebase client ID.
    let config = GIDConfiguration(clientID: clientID)
    GIDSignIn.sharedInstance.configuration = config

    // Retrieve the topmost view controller to present the Google Sign-In UI.
    guard let controller = TopViewController.shared.topViewController() else {
        print("LoginViewModel: \(#function) Top Controller not found.")
        return
    }

    // Start the Google Sign-In process, presenting it from the retrieved view controller.
    GIDSignIn.sharedInstance.signIn(withPresenting: controller) { [unowned self] result, error in
        guard error == nil else {
            print("LoginViewModel: \(#function) Google Login Error: \(String(describing: error)).")
            return
        }

        // Ensure the user and their ID token are available in the result.
        guard let user = result?.user, let idToken = user.idToken?.tokenString else { return }

        // Extract user details like first name, last name, and email from the profile.
        let firstName = user.profile?.givenName ?? ""
        let lastName = user.profile?.familyName ?? ""
        let email = user.profile?.email ?? ""

        // Create a Firebase authentication credential using the Google ID token.
        let credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken:

        // Set loading state to true while performing Firebase login.
        self.showGoogleLoading = true

        // Call the helper function to authenticate with Firebase using the credential.
        self.performFirebaseLogin(showGoogleLoading: showGoogleLoading, credential: credential,
                                  loginType: .Google, userData: (firstName, lastName, email))
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;&lt;code&gt;onGoogleLoginClick()&lt;/code&gt;&lt;/strong&gt; function initializes Google Sign-In with Firebase's client ID, displays the sign-in UI, and retrieves user details and a Firebase credential upon success. It then calls performFirebaseLogin().&lt;/p&gt;

&lt;h4&gt;
  
  
  Handle Firebase Authentication
&lt;/h4&gt;

&lt;p&gt;Now, let’s implement the performFirebaseLogin() function to manage the Firebase authentication process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private func performFirebaseLogin(showGoogleLoading: Bool = false, credential: AuthCredential,
                                  loginType: LoginType, userData: (String, String,

    // Show the loading indicator while logging in.
    self.showGoogleLoading = showGoogleLoading

    // Sign in to Firebase using the provided credential.
    FirebaseProvider.auth
        .signIn(with: credential) { [weak self] result, error in
            guard let self else { return }

            // Handle errors during Firebase sign-in.
            if let error {
                self.showGoogleLoading = false
                print("LoginViewModel: \(#function) Firebase Error: \(error), with type Apple login.")
                self.alert = .init(message: "Server error")
                self.showAlert = true
            } else if let result {
                // On successful sign-in, stop loading and create a user object.
                self.showGoogleLoading = false
                let user = User(id: result.user.uid, firstName: userData.0, lastName: userData.1,
                                   emailId: userData.2, phoneNumber: nil, loginType: loginType)

                // Save user preferences and mark the user as verified.
                self.preference.user = user
                self.preference.isVerifiedUser = true
                print("LoginViewModel: \(#function) Logged in User: \(result.user)")
            } else {
                self.alert = .init(message: "Contact Support")
                self.showAlert = true
            }
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function authenticates the user with Firebase, updates user preferences upon success, and manages errors by logging them and showing alerts.&lt;/p&gt;

&lt;h4&gt;
  
  
  AppPreference Class:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Observable
class AppPreference {

    enum Key: String {
        case isVerifiedUser = "is_verified_user"
        case user           = "user"
    }

    private let userDefaults: UserDefaults

    init() {
        self.userDefaults = UserDefaults.standard
        self.isVerifiedUser = userDefaults.bool(forKey: Key.isVerifiedUser.rawValue)
    }

    public var isVerifiedUser: Bool {
        didSet {
            userDefaults.set(isVerifiedUser, forKey: Key.isVerifiedUser.rawValue)
        }
    }

    public var user: User? {
        get {
            do {
                let data = userDefaults.data(forKey: Key.user.rawValue)
                if let data {
                    let user = try JSONDecoder().decode(User.self, from: data)
                    return user
                }
            } catch let error {
                print("Preferences \(#function) json decode error: \(error).")
            }
            return nil
        } set {
            do {
                let data = try JSONEncoder().encode(newValue)
                userDefaults.set(data, forKey: Key.user.rawValue)
            } catch let error {
                print("Preferences \(#function) json encode error: \(error).")
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  User Data Class:
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public struct User: Identifiable, Codable, Hashable {

    public var id: String
    public var firstName: String?
    public var lastName: String?
    public var emailId: String?
    public var phoneNumber: String?
    public let loginType: LoginType

    enum CodingKeys: String, CodingKey {
        case id
        case firstName = "first_name"
        case lastName = "last_name"
        case emailId = "email_id"
        case phoneNumber = "phone_number"
        case loginType = "login_type"
    }
}

public enum LoginType: String, Codable {
    case Apple = "apple"
    case Google = "google"
    case Phone = "phone"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Handle Callback URL in AppDelegate
&lt;/h4&gt;

&lt;p&gt;For Google Sign-In to work correctly, you must handle the &lt;strong&gt;callback URL&lt;/strong&gt; when the user completes the sign-in process.&lt;/p&gt;

&lt;p&gt;You can manage this to the &lt;code&gt;AppDelegate&lt;/code&gt; by overriding the &lt;code&gt;application(_:open:options:)&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Here is the updated &lt;code&gt;AppDelegate&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AppDelegate: NSObject, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -&amp;gt; Bool {
        FirebaseProvider.configureFirebase()
        return true
    }

    // Add this method to handle the Google Sign-In callback URL
    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -&amp;gt; Bool {
        return GIDSignIn.sharedInstance.handle(url)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that Google Sign-In can be completed and returned to the app.&lt;/p&gt;

&lt;h4&gt;
  
  
  Test the Integration
&lt;/h4&gt;

&lt;p&gt;Once everything is set up, run the app and try logging in with Google. You should see the Google login flow, and upon successful authentication, you will be logged into Firebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apple Login Integration
&lt;/h2&gt;

&lt;p&gt;Apple Sign-In with Firebase allows users to log in securely using their Apple ID. Below is a comprehensive guide for integrating Apple Sign-In into your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-requisites
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Apple Developer Account Configuration:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Register your app in the Apple Developer portal.&lt;/li&gt;
&lt;li&gt;Set up the &lt;strong&gt;“Sign in with Apple”&lt;/strong&gt; capability to the provisioning profile.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Firebase Console:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable the Apple sign-in provider in your Firebase project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Xcode Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the Apple Sign-In capability to your Xcode project.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;If you want to refer to the official doc for all basic setup for &lt;strong&gt;Sign in with Apple&lt;/strong&gt;, refer to this &lt;a href="https://firebase.google.com/docs/auth/ios/apple" rel="noopener noreferrer"&gt;Firebase Doc.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Steps to Configure the Apple Developer Portal
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Create an App ID:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log in to your &lt;a href="https://developer.apple.com/" rel="noopener noreferrer"&gt;Apple Developer Account&lt;/a&gt; and navigate to &lt;strong&gt;Certificates, Identifiers &amp;amp; Profiles &amp;gt; Identifiers&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click the + button to create a new App ID and enable Sign in with Apple for your app’s Bundle ID.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Generate Certificates &amp;amp; Provisioning Profiles:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create and download the necessary Development and Distribution certificates.&lt;/li&gt;
&lt;li&gt;Generate and install provisioning profiles for both environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Xcode Project Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Add Apple Sign-In Capability:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open your project in Xcode.&lt;/li&gt;
&lt;li&gt;Go to Signing &amp;amp; Capabilities and add the Sign in with Apple capability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Update the Info.plist:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add necessary permissions and configurations for Apple Sign-In in your Info.plist as follows:&lt;/li&gt;
&lt;li&gt;Go to the app target &amp;gt; Signing &amp;amp; capabilities &amp;gt; click + &amp;gt; search for &lt;strong&gt;&lt;code&gt;Sign in with Apple&lt;/code&gt;&lt;/strong&gt; and add it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Generate and Use a Nonce for Secure Authentication
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Nonce Generator Class:&lt;/strong&gt; This class generates a secure random string (nonce) and hashes it using the SHA-256 algorithm. It ensures that the authentication process is safe and helps to prevent replay attacks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import CryptoKit
import Foundation

// A class for generating a secure random string (nonce) and hashing strings using SHA-256.
class NonceGenerator {

    // Generates a random nonce string of specified length (default is 32).
    static public func randomNonceString(length: Int = 32) -&amp;gt; String {

        var result = "" // Final nonce string to be returned.
        var remainingLength = length // Tracks how many characters are still needed.
        let charset: [Character] = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._") // Allowed characters for the nonce.

        // Ensures the requested length is valid (greater than 0).
        precondition(length &amp;gt; 0)

        // Generate characters until the required length is met.
        while remainingLength &amp;gt; 0 {

            // Generate an array of 16 random bytes.
            let randoms: [UInt8] = (0 ..&amp;lt; 16).map { _ in
                var random: UInt8 = 0

                // Use a secure random number generator to generate each byte.
                let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &amp;amp;random)
                if errorCode != errSecSuccess {
                    fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
                }
                return random
            }

            // Map each random byte to a character in the charset if within bounds.
            randoms.forEach { random in
                if remainingLength == 0 { // Stop if the required length is reached.
                    return
                }

                if random &amp;lt; charset.count { // Ensure the random value maps to the charset range.
                    result.append(charset[Int(random)])
                    remainingLength -= 1 // Decrease the remaining length.
                }
            }
        }
        return result // Return the generated nonce string.
    }

    // Hashes a given input string using the SHA-256 algorithm.
    public static func sha256(_ input: String) -&amp;gt; String {
        let inputData = Data(input.utf8) // Convert the input string to Data.
        let hashedData = SHA256.hash(data: inputData) // Generate the SHA-256 hash.
        let hashString = hashedData.compactMap {
            return String(format: "%02x", $0) // Convert each byte of the hash to a hexadecimal string.
        }.joined() // Join all hexadecimal values into a single string.
        return hashString // Return the hash string.
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code snippet helps generate a nonce and hash it for safe and secure Apple Sign-In authentication.&lt;/p&gt;

&lt;p&gt;This NonceGenerator class provides two main utilities:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. randomNonceString(length: Int):
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Generates a cryptographically secure random string (nonce) of a specified length.&lt;/li&gt;
&lt;li&gt;The generated string uses a predefined set of characters (0–9, A-Z, a-z, -, _) and ensures unpredictability by using the SecRandomCopyBytes function for randomness.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. sha256(_ input: String):
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;It takes a string as input and returns its SHA-256 hash as a hexadecimal string.&lt;/li&gt;
&lt;li&gt;This can be used to securely hash data for comparison or storage.The class is useful in scenarios requiring secure, unpredictable values (e.g., for authentication, cryptographic protocols) and hashing operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Handle the Apple Sign-In Flow
&lt;/h3&gt;

&lt;p&gt;We’ll break down the process into two parts:&lt;br&gt;
 —  Triggering the Apple Sign-In Process&lt;br&gt;
 —  Handling the Apple Sign-In Response&lt;/p&gt;
&lt;h4&gt;
  
  
  1. Triggering the Apple Sign-In Process
&lt;/h4&gt;

&lt;p&gt;To initiate the Apple Sign-In flow, we must create a request and delegate its handling to a custom class. This ensures that the sign-in process is managed separately, making the code easier to maintain.&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 1: Create a Separate Delegate Class
&lt;/h4&gt;

&lt;p&gt;In our project, we handle Apple Sign-In’s delegate methods in a separate class, &lt;code&gt;SignInWithAppleDelegates.swift&lt;/code&gt; to keep the logic isolated.&lt;/p&gt;

&lt;p&gt;Here's the structure of the delegate class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class SignInWithAppleDelegates: NSObject {

    private let signInSucceeded: (String, String, String, String) -&amp;gt; Void

    init(signInSucceeded: @escaping (String, String, String, String) -&amp;gt; Void) {
        self.signInSucceeded = signInSucceeded
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Handling Apple Sign-In Button Click
&lt;/h3&gt;

&lt;p&gt;When the user clicks the Apple Sign-In button, we generate a nonce (a random string) and start the authorization request. The nonce is hashed using SHA-256 for security.&lt;/p&gt;

&lt;p&gt;Let’s update LoginViewModel’s method for the Apple login action.&lt;/p&gt;

&lt;p&gt;Here’s how you can set up the button action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func onAppleLoginClick() {
    // Generate a random nonce to use during the Apple Sign-In process for security.
    self.currentNonce = NonceGenerator.randomNonceString()

    // Create a new Apple ID authorization request.
    let request = ASAuthorizationAppleIDProvider().createRequest()

    // Specify the user information that the app wants to access (full name and email).
    request.requestedScopes = [.fullName, .email]

    // Hash the generated nonce using SHA-256 and assign it to the request for verification.
    request.nonce = NonceGenerator.sha256(currentNonce)

    // Initialize the delegate to handle the Apple Sign-In process's callbacks.
    // The delegate is responsible for managing the successful authentication and passing the user data.
    appleSignInDelegates = SignInWithAppleDelegates { (token, fName, lName, email)  in

        // Create an OAuth credential for Firebase using the token received from Apple.
        let credential = OAuthProvider.credential(providerID: AuthProviderID.apple, idToken: token, rawNonce:
        self.showAppleLoading = true

        // Call a separate method to complete the Firebase login using the created credential.
        self.performFirebaseLogin(showAppleLoading: self.showAppleLoading, credential: credential,
                                  loginType: .Apple, userData: (fName, lName, email))
    }

    // Create an authorization controller to manage the Apple Sign-In flow.
    let authorizationController = ASAuthorizationController(authorizationRequests: [request])

    // Assign the delegate to handle the authorization callbacks.
    authorizationController.delegate = appleSignInDelegates

    // Start the Apple Sign-In process by presenting the authorization request to the user.
    authorizationController.performRequests()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Firebase Login Integration
&lt;/h4&gt;

&lt;p&gt;Next, we update our Firebase login method to handle both Google and Apple logins. We pass in the credential (from Apple Sign-In) and user data (name, email) to Firebase for authentication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private func performFirebaseLogin(showGoogleLoading: Bool = false, showAppleLoading: Bool = false,
                                  credential: AuthCredential, loginType: LoginType, userData: (String, String,

    // Set the loading states for Google and Apple login to indicate the process has started.
    self.showGoogleLoading = showGoogleLoading
    self.showAppleLoading = showAppleLoading

    // Attempt to sign in with Firebase using the provided credentials.
    FirebaseProvider.auth
        .signIn(with: credential) { [weak self] result, error in
            guard let self else { return }

            // Handle any errors during the Firebase sign-in process.
            if let error {
                self.showGoogleLoading = false
                self.showAppleLoading = false
                print("LoginViewModel: \(#function) Firebase Error: \(error), with type Apple login.")
                self.alert = .init(message: "Server error")
                self.showAlert = true
            } else if let result {
                // Reset the loading states as the login process is complete.
                self.showGoogleLoading = false
                self.showAppleLoading = false

                // Create a User object using the signed-in user's details.
                let user = User(id: result.user.uid, firstName: userData.0, lastName: userData.1,
                                emailId: userData.2, phoneNumber: nil, loginType: loginType)

                // Save the user object and mark the user as verified in preferences.
                self.preference.user = user
                self.preference.isVerifiedUser = true
                print("LoginViewModel: \(#function) Logged in User: \(result.user)")
            } else {
                // Handle unexpected cases where neither error nor result is present.
                self.alert = .init(message: "Contact Support")
                self.showAlert = true
            }
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sign in to Firebase using the OAuthProvider.credential generated during the Apple Sign-In process.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Handle the Apple Sign-In Response
&lt;/h3&gt;

&lt;p&gt;Once the user completes the Apple Sign-In process, we must handle the response, including errors or successful sign-ins.&lt;/p&gt;

&lt;h4&gt;
  
  
  Apple Sign-In Delegate Methods
&lt;/h4&gt;

&lt;p&gt;We extend the &lt;code&gt;SignInWithAppleDelegates&lt;/code&gt; class to implement the &lt;code&gt;ASAuthorizationControllerDelegate&lt;/code&gt; methods.&lt;/p&gt;

&lt;p&gt;Here's how we manage the successful sign-in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Extend the `SignInWithAppleDelegates` class to handle Apple Sign-In responses.
extension SignInWithAppleDelegates: ASAuthorizationControllerDelegate {

    // Method called when the authorization process completes successfully.
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {

        // Attempt to retrieve the Apple ID credentials from the authorization response.
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {

            // Fetch the identity token from the Apple ID credential.
            guard let appleIDToken = appleIDCredential.identityToken else {
                print("SignInWithAppleDelegates: \(#function) Unable to fetch identity token.")
                return
            }

            // Convert the identity token to a String format for further processing.
            guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
                print("SignInWithAppleDelegates: \(#function) Unable to serialize token string from data: \(appleIDToken.debugDescription).")
                return
            }

            // Extract additional user information (first name, last name, email) from the Apple ID credential.
            let firstName = appleIDCredential.fullName?.givenName ?? ""
            let lastName = appleIDCredential.fullName?.familyName ?? ""
            let email = appleIDCredential.email ?? ""

            // Call a custom method to handle successful sign-in, passing the token and user information.
            self.signInSucceeded(idTokenString, firstName, lastName, email)
        }
    }

    // Method called when the authorization process fails.
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        print("SignInWithAppleDelegates: \(#function) Apple login complete with error: \(error).")
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This delegate method extracts the user data (first name, last name, email) and the identity token, which is then used to authenticate the user with Firebase.&lt;/p&gt;

&lt;p&gt;It provides a robust mechanism for managing the Apple Sign-In process, ensuring both user data extraction and error handling are addressed efficiently.&lt;/p&gt;

&lt;p&gt;Now, Let’s run the app,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fd0bjjk6wfpf2fwyl97hn.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fd0bjjk6wfpf2fwyl97hn.gif" alt="Apple Login" width="295" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Phone Login Integration
&lt;/h2&gt;

&lt;p&gt;Phone authentication is an effective way to enhance user experience while ensuring secure access. From configuring Firebase to implementing phone number verification and OTP handling.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To explore the step-by-step instruction on phone login feature implementations, check out our full guide at &lt;strong&gt;&lt;a href="https://canopas.com/firebase-authentication-google-apple-and-phone-login-to-ios-app" rel="noopener noreferrer"&gt;Canopas Blog&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;If you like what you read, be sure to hit 💖 button! — as a writer it means the world!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.&lt;/p&gt;

&lt;p&gt;Happy coding!👋&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>firebase</category>
      <category>programming</category>
    </item>
    <item>
      <title>Integrating Live Activity and Dynamic Island in iOS - Part 2</title>
      <dc:creator>Nandani Sharma</dc:creator>
      <pubDate>Wed, 04 Dec 2024 10:29:31 +0000</pubDate>
      <link>https://dev.to/canopassoftware/integrating-live-activity-and-dynamic-island-in-ios-part-2-51fo</link>
      <guid>https://dev.to/canopassoftware/integrating-live-activity-and-dynamic-island-in-ios-part-2-51fo</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Welcome to Part 2 of our guide on integrating Live Activities and Dynamic Island in iOS. In &lt;strong&gt;&lt;a href="https://canopas.com/integrating-live-activity-and-dynamic-island-in-i-os-a-complete-guide" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;&lt;/strong&gt;, we explored the fundamentals—how to set up a Live Activity, manage its lifecycle, and design layouts that work seamlessly on the Lock Screen and Dynamic Island.&lt;/p&gt;

&lt;p&gt;In this part, we’ll take things further by adding advanced functionality to elevate the user experience. We’ll learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Animate updates for smoother transitions.&lt;/li&gt;
&lt;li&gt;Handling live activity from a push notification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Let's get started…&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Animate state updates in Live Activity
&lt;/h2&gt;

&lt;p&gt;Animations in Live Activities make updates visually engaging and draw the user's attention to important changes. Starting with iOS 17, Live Activities automatically animate data updates using default animations, or we can customize them using SwiftUI animations. &lt;/p&gt;

&lt;h3&gt;
  
  
  Key Points About Animations in Live Activities
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Default Animations:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Text changes are animated with smooth transitions, like a blur effect.&lt;/li&gt;
&lt;li&gt;Image and SF Symbol updates include fade-in effects or other content transitions.&lt;/li&gt;
&lt;li&gt;When adding or removing views, the system uses fade animations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Customizing Animations:
&lt;/h4&gt;

&lt;p&gt;You can replace default animations with SwiftUI's built-in transitions and animations. Options include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transitions:&lt;/strong&gt; Use effects like &lt;code&gt;.opacity&lt;/code&gt;, &lt;code&gt;.move(edge:)&lt;/code&gt;, &lt;code&gt;.slide&lt;/code&gt;, or &lt;code&gt;.push(from:)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content Transitions:&lt;/strong&gt; Apply animations directly to text, such as &lt;code&gt;.contentTransition(.numericText())&lt;/code&gt; for numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Animations:&lt;/strong&gt; Add &lt;code&gt;.animation(_:value:)&lt;/code&gt; to views for more control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, we can configure a content transition for queue position text as shown in this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Text("\(position)")
   .font(.system(size: 36, weight: .semibold)).lineSpacing(48).foregroundColor(Color("TextPrimary"))
   .contentTransition(.numericText())
   .animation(.spring(duration: 0.2), value: position)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Animating View Updates:
&lt;/h4&gt;

&lt;p&gt;To animate a view when data changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the &lt;code&gt;.id()&lt;/code&gt; modifier to associate the view with a data model that conforms to Hashable.&lt;/li&gt;
&lt;li&gt;Apply a transition or animation based on the associated value.&lt;/li&gt;
&lt;li&gt;Example: When the queue position changes, the image transitions from the bottom.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct QueueIllustration :View {
    let position: Int

    var imageName: String {
        if position &amp;lt; 5 {
            return "queue4"
        } else if position &amp;lt; 9 {
            return "queue3"
        } else if position &amp;lt; 25 {
            return "queue2"
        } else {
            return "queue1"
        }
    }

    var body: some View {
        Image(uiImage: UIImage(named: imageName)!)
            .resizable().frame(width: 100, height: 100)
            .scaledToFit().id(imageName)
            .transition(.push(from: .bottom))
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Limitations:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Animations have a maximum duration of 2 seconds.&lt;/li&gt;
&lt;li&gt;On the devices with Always-On displays, animations are disabled to save battery life. You can use the &lt;code&gt;isLuminanceReduced&lt;/code&gt; environment value to detect when the Always-On display is active.&lt;/li&gt;
&lt;li&gt;On devices running iOS 16 or earlier, only system-defined animations like &lt;code&gt;.move&lt;/code&gt; or &lt;code&gt;.slide&lt;/code&gt; are supported, and custom animations like &lt;code&gt;withAnimation&lt;/code&gt; are ignored.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  5. Disabling Animations
&lt;/h4&gt;

&lt;p&gt;If multiple views in your Live Activity are updated simultaneously, consider disabling animations for less important changes to focus user attention. &lt;/p&gt;

&lt;p&gt;To disable animations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;.transition(&lt;/code&gt;.identity&lt;code&gt;)&lt;/code&gt; to prevent transitions.&lt;/li&gt;
&lt;li&gt;Pass &lt;code&gt;nil&lt;/code&gt; to the animation parameter
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;withAnimation(nil) {
    // Updates without animations
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling live activity from a push notification
&lt;/h2&gt;

&lt;p&gt;One of the most powerful features of Live Activities is their ability to receive updates via push notifications. This enables us to keep the content of a Live Activity synchronized with real-time data, ensuring users always have the latest information without manual intervention.&lt;/p&gt;

&lt;p&gt;With &lt;strong&gt;ActivityKit&lt;/strong&gt;, we can update or end Live Activities directly from the server by using push tokens. Starting with &lt;strong&gt;iOS 17.2&lt;/strong&gt;, we can even start new Live Activities using push notifications. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Push notification capability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before we go ahead make sure you have added the push notification capability in your application target.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1ukp0csoatvdv3aak489.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1ukp0csoatvdv3aak489.png" alt="Push notification capability" width="800" height="149"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Started with Push Tokens
&lt;/h3&gt;

&lt;p&gt;Push tokens are the key to communicating with Live Activities on a user’s device.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Push-to-start tokens
&lt;/h3&gt;

&lt;p&gt;A push-to-start token is a secure identifier generated by the system, which enables the server to start a new Live Activity remotely via Apple Push Notification service (APNs). This token is device-specific and tied to the user’s current app state. When the server sends a valid payload using this token, the system creates a Live Activity on the user’s device.&lt;/p&gt;

&lt;p&gt;We can use the &lt;code&gt;pushToStartTokenUpdates&lt;/code&gt; asynchronous API provided by ActivityKit to listen for and retrieve push-to-start tokens. This API notifies our app whenever a new token is available.&lt;/p&gt;

&lt;p&gt;Here’s how to implement it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; func observePushToStartToken() {
        if #available(iOS 17.2, *), areActivitiesEnabled() {
            Task {
                for await data in Activity&amp;lt;WaitTimeDemoAttributes&amp;gt;.pushToStartTokenUpdates {
                    let token = data.map {String(format: "%02x", $0)}.joined()
                       // Send token to the server
                    }
            }

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

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Activity&amp;lt;MyAttributes&amp;gt;.pushToStartTokenUpdates&lt;/code&gt;:  an asynchronous sequence that continuously listens for new push-to-start tokens generated by the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Push-to-Update token
&lt;/h3&gt;

&lt;p&gt;Once we have started a Live Activity on the user's device, we might need to update it periodically, such as to reflect progress, changes in status, or updated data. This is where &lt;strong&gt;push token updates&lt;/strong&gt; come into play. When the app starts a Live Activity, it will receive a push token from ActivityKit, which we can use to send updates to the Live Activity via push notifications.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;pushTokenUpdates&lt;/code&gt; API is used to retrieve the push token required for sending updates to an existing Live Activity. This token is a &lt;strong&gt;unique identifier&lt;/strong&gt; for each Live Activity that the app can use to send push notifications to the device.&lt;/p&gt;

&lt;p&gt;Here's how to get an update token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func observePushUpdateToken() {

    // Check if iOS version 17.2 or later is available and if activities are enabled
    if #available(iOS 17.2, *), areActivitiesEnabled() {

        // Start a task to observe updates from ActivityKit
        Task {

             // Listen for any updates from a Live Activity with WaitTimeDemoAttributes
            for await activityData in Activity&amp;lt;WaitTimeDemoAttributes&amp;gt;.activityUpdates {

                // Listen for updates to the push token associated with the Live Activity
                Task {
                    // Iterate through the push token updates

                    for await tokenData in activityData.pushTokenUpdates {

                        // Convert the token data to a hexadecimal string
                        let token = tokenData.map { String(format: "%02x", $0) }.joined()

                        // Obtain the associated booking ID from the activity attributes
                        let bookingId = activityData.attributes.bookingId

                        // Prepare the data dictionary to pass to the callback
                        let data = [
                            "token": token,
                            "bookingId": activityData.attributes.bookingId
                        ]

                        // TODO Send data to the server
                    }
                }
            }
        }
    }
}

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

&lt;/div&gt;



&lt;p&gt;After obtaining the update push token, we can use it to send push notifications to update the Live Activity. The token may change over time, so the server needs to be prepared to handle updates to the push token. Whenever a new token is received, it should be updated on the server and invalidated on previous tokens.&lt;/p&gt;

&lt;p&gt;The format of the push notification is similar to the one we used to start a Live Activity, but this time, we're sending an update to the content or state of the existing Live Activity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Push-to-Start Tokens to Start a Live Activity
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Payload for Starting a Live Activity
&lt;/h4&gt;

&lt;p&gt;When the server needs to start a Live Activity, it sends a JSON payload to APNs, using the push-to-start token as an identifier. The system will create the Live Activity on the device upon receiving this payload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payloads(all fields are required):

"aps": {
        "timestamp": '$(date +%s)',
        "event": "start",
        "content-state": {
            "progress": 0.1,
            "currentPositionInQueue": 8
        },
        "attributes-type": "WaitTimeDemoAttributes",
        "attributes": {
            "waitlistName": "For Testing",
            "waitlistId": "",
            "bookingId": ""
        },
      "alert": {
            "title": "",
            "body": "",
            "sound": "default"
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;timestamp&lt;/code&gt;: Represents the current time in UNIX timestamp format to indicate when the event occurred.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;event&lt;/code&gt;: The type of event; in this case, we’re starting a Live Activity ("start")&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;content-state&lt;/code&gt;: Holds information related to the current state of the activity. Note: The key should match with Live activity Attributes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;attributes-type&lt;/code&gt;: Specifies the type of attributes for the Live Activity. A Live activity data holder, which contains the state and attributes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;attributes&lt;/code&gt;: Contains the immutable data of live activity.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;alert&lt;/code&gt;: Configures a notification that may appear to the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Headers for Push Notification
&lt;/h4&gt;

&lt;p&gt;When sending this push notification, the following headers are required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apns-topic&lt;/code&gt;: The topic indicates the bundle identifier and specifies the push type for Live Activities.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apns-topic: &amp;lt;bundleId&amp;gt;.push-type.liveactivity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apns-push-type&lt;/code&gt;: Specify that this is a push notification for a Live Activity.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apns-push-type: liveactivity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apns-priority&lt;/code&gt;: Set the value for the priority to 5 or 10 (We'll discuss this in next section).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apns-priority: 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;authorization&lt;/code&gt;: The bearer token used for authenticating the push notification request.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authorization: bearer $AUTHENTICATION_TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Behaviour on the Device
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Starting the Live Activity:
&lt;/h4&gt;

&lt;p&gt;When the device receives the push notification, the system automatically creates a new Live Activity using the provided attributes.&lt;br&gt;
The app is woken up in the background to download any assets or perform setup tasks.&lt;/p&gt;
&lt;h4&gt;
  
  
  2. Token Refresh:
&lt;/h4&gt;

&lt;p&gt;Once the Live Activity starts, the system generates a new push token for updates. This token should be send to server for managing future updates or ending the activity.&lt;/p&gt;
&lt;h2&gt;
  
  
  Sending Push Notifications to Update the Live Activity
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Payloads(all fields are required):
 "aps": {
          "timestamp": '$(date +%s)',
          "event": "update",
          "content-state": {
              "progress": 0.941,
              "currentPositionInQueue": 10
          }
          "alert": {
            "title": "",
            "body": " ",
            "sound": "anysound.mp4"
        }
      }   

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

&lt;/div&gt;


&lt;p&gt;Once we have the payload and headers set up, we'll send the push notification from the server to APNs, which will then deliver it to the user’s device. When the device receives the update notification, ActivityKit will apply the changes to the existing Live Activity and update the content on the Dynamic Island or Lock Screen.&lt;/p&gt;

&lt;p&gt;Once a Live Activity has ended, the system ignores any further push notifications sent to that activity. This means if we send an update after the Live Activity is complete, the system won't process it, and the information may no longer be relevant. If a push notification is not delivered or is ignored after the activity ends, the Live Activity might display outdated or incorrect information.&lt;/p&gt;
&lt;h2&gt;
  
  
  Sending Push Notifications to End the Live Activity
&lt;/h2&gt;

&lt;p&gt;To end a Live Activity, we can send a push notification with the event field set to end. This marks the activity as complete. &lt;/p&gt;

&lt;p&gt;By default, a Live Activity stays on the Lock Screen for up to four hours after it ends, giving users a chance to refer to the latest information. However, we can control when the Live Activity disappears by adding a &lt;code&gt;dismissal-date&lt;/code&gt; field in the push notification's payload.&lt;/p&gt;

&lt;p&gt;For example, in a waitlist scenario, when the waitlist is over and users are served, we can send an update with an "end" event, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "aps": {
        "timestamp": 1685952000,
        "event": "end",
        "dismissal-date": 1685959200,
        "content-state": {
              "progress": 0.1,
              "currentPositionInQueue": 1
         }
    }
}

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

&lt;/div&gt;



&lt;p&gt;To remove Live Activity immediately after it ends, set the &lt;code&gt;dismissal-date&lt;/code&gt; to a timestamp in the past. For example: &lt;code&gt;"dismissal-date":&lt;/code&gt; 1663177260. Alternatively, we can set a custom dismissal time within a four-hour window.&lt;/p&gt;

&lt;p&gt;If we don’t include a &lt;code&gt;dismissal-date&lt;/code&gt;, the system will handle the dismissal automatically, using its default behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Activity relevance-score
&lt;/h3&gt;

&lt;p&gt;When our app starts multiple Live Activities, we can control which one appears in the Dynamic Island by setting a relevance-score in the JSON payload.&lt;/p&gt;

&lt;p&gt;If we don’t set a score or if all Live Activities have the same score, the system will show the first one started in the Dynamic Island. To highlight more important updates, assign a higher relevance score (e.g., 100), and for less important ones, use a lower score (e.g., 50).&lt;/p&gt;

&lt;p&gt;Make sure to track and update relevance scores as needed to control which Live Activity appears in the Dynamic Island. Here's an example with a high relevance score of 100:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "aps": {
        "timestamp": 1685952000,
        "event": "update",
        "relevance-score": 100,
        "content-state": {
            ....
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures that the most important Live Activity update is shown in the Dynamic Island.&lt;/p&gt;

&lt;h3&gt;
  
  
  Push Update frequency for Live Activities
&lt;/h3&gt;

&lt;p&gt;ActivityKit push notifications come with certain limits on how frequently they can be sent to avoid overwhelming users. This limit is referred to as the &lt;strong&gt;ActivityKit notification budget&lt;/strong&gt;, which restricts the number of updates our app can send per hour. To manage this budget and prevent throttling, we can adjust the priority of our push notifications using the apns-priority header.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setting the Priority
&lt;/h4&gt;

&lt;p&gt;By default, if we don’t specify a priority for push notifications, they will be sent with a priority of 10 (immediate delivery), and this will count toward ActivityKit push notification budget. If we exceed this budget, the system may throttle our notifications. To reduce the chance of hitting the limit, consider using a lower priority (priority 5) for updates that don’t require immediate attention.&lt;/p&gt;

&lt;h4&gt;
  
  
  Handling Frequent Updates
&lt;/h4&gt;

&lt;p&gt;In some use cases, such as tracking a live sports event, we may need to update our Live Activity more frequently than usual. However, updating the Live Activity too often could quickly exhaust the notification budget, causing delays or missed updates.&lt;/p&gt;

&lt;p&gt;To overcome this, we can enable frequent updates for our Live Activity. This allows our app to send updates at a higher frequency without hitting the budget limits. To enable this feature, we need to modify our app's &lt;strong&gt;Info.plist&lt;/strong&gt; file:&lt;/p&gt;

&lt;p&gt;Open the Info.plist file and add the following entry:&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;key&amp;gt;NSSupportsLiveActivitiesFrequentUpdates&amp;lt;/key&amp;gt;
&amp;lt;true/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This change will allow our app to send frequent ActivityKit push notifications. However, users can turn off this feature in their device settings.&lt;/p&gt;

&lt;h4&gt;
  
  
  Detecting and Handling Frequent Update Settings
&lt;/h4&gt;

&lt;p&gt;If a user has deactivated frequent updates for the app, we can detect this and display a message asking them to turn it back on. Additionally, we can adjust our update frequency to match the user's preferences.&lt;/p&gt;

&lt;p&gt;We can also subscribe to updates regarding the status of frequent updates via the frequentPushEnablementUpdates stream, which allows us to respond to changes in the notification settings in real-time.&lt;/p&gt;

&lt;h4&gt;
  
  
  In Summary
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;priority 5&lt;/strong&gt; for less urgent updates to avoid hitting the notification budget.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;priority 10&lt;/strong&gt; for critical updates to ensure timely delivery. &lt;/li&gt;
&lt;li&gt;Enable frequent updates in the &lt;strong&gt;Info.plist&lt;/strong&gt; for use cases requiring high-frequency updates, like live sports tracking.&lt;/li&gt;
&lt;li&gt;Respect users’ preferences for frequent updates and adjust the app accordingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Further, we've discussed the intricacies of setting up, configuring, and &lt;strong&gt;Testing Live Activity Push Notifications&lt;/strong&gt; to create a seamless user experience. &lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  Read the complete guide on &lt;strong&gt;&lt;a href="https://canopas.com/integrating-live-activity-and-dynamic-island-in-i-os-a-complete-guide-part-2" rel="noopener noreferrer"&gt;Integrating Live Activity and Dynamic Island in iOS&lt;/a&gt;&lt;/strong&gt;.
&lt;/h2&gt;

&lt;p&gt;If you like what you read, be sure to hit 💖 button! — as a writer it means the world!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.&lt;/p&gt;

&lt;p&gt;Happy coding!👋&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
      <category>programming</category>
    </item>
    <item>
      <title>How To Create Easy Pagination In Jetpack Compose</title>
      <dc:creator>Nandani Sharma</dc:creator>
      <pubDate>Tue, 03 Dec 2024 10:18:15 +0000</pubDate>
      <link>https://dev.to/canopassoftware/how-to-create-easy-pagination-in-jetpack-compose-2hmh</link>
      <guid>https://dev.to/canopassoftware/how-to-create-easy-pagination-in-jetpack-compose-2hmh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When building apps with long lists of data, the &lt;strong&gt;Jetpack Paging Library&lt;/strong&gt; is often the go-to solution. However, it can be overkill for scenarios where you need basic pagination or when working with APIs and databases that provide simple offset or query-based data retrieval.&lt;/p&gt;

&lt;p&gt;In this blog post, I’ll show you how to implement smooth pagination in Jetpack Compose without using the Paging 3 library. Instead, we’ll use Firestore queries and Compose’s LazyColumn to achieve an elegant, lightweight solution that is easier to understand and adapt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use This Approach?
&lt;/h2&gt;

&lt;p&gt;The Jetpack Paging Library is powerful, but it has its complexities and a steeper learning curve. Here are some scenarios where this custom approach shines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity:&lt;/strong&gt; You have basic pagination needs and prefer a more straightforward solution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control:&lt;/strong&gt; You want full customization over how and when data is fetched and displayed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Firestore Optimization:&lt;/strong&gt; This approach takes advantage of Firestore’s native query capabilities, making it ideal for apps using Firebase.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this tutorial, you’ll have a working implementation that can dynamically load more data as the user scrolls through a list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Setting Up Dependencies
&lt;/h2&gt;

&lt;p&gt;To get started, ensure your project includes the necessary dependencies for Firebase, Hilt, and Jetpack Compose. You can follow these steps to &lt;a href="https://firebase.google.com/docs/android/setup#console" rel="noopener noreferrer"&gt;set up your Firebase project&lt;/a&gt; and include the required dependencies.&lt;/p&gt;

&lt;p&gt;Add these to your &lt;code&gt;build.gradle&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Firebase
implementation(platform("com.google.firebase:firebase-bom:33.6.0"))
implementation("com.google.firebase:firebase-common-ktx:21.0.0")
implementation("com.google.firebase:firebase-firestore-ktx:25.1.1")

// Hilt for Dependency Injection
implementation("com.google.dagger:hilt-android:2.51.1")
kapt("com.google.dagger:hilt-android-compiler:2.51.1")
implementation("androidx.hilt:hilt-navigation-compose:1.2.0")


// Coil for Image Loading
implementation("io.coil-kt:coil-compose:2.7.0")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These libraries will enable Firestore integration, dependency injection with Hilt, and image rendering with Coil.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Firestore Integration
&lt;/h2&gt;

&lt;p&gt;To interact with Firestore, define a &lt;code&gt;Movie&lt;/code&gt; data model and a &lt;code&gt;MovieService&lt;/code&gt; for fetching data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Define the Movie Model
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Movie&lt;/code&gt; class represents a movie document in Firestore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data class Movie(
    val id: String = UUID.randomUUID().toString(),
    val title: String = "",
    val posterUrl: String = "",
    val description: String = "",
    var createdAt: Long = System.currentTimeMillis()
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use the &lt;code&gt;createdAt&lt;/code&gt; field for sorting and pagination.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the Movie Service
&lt;/h3&gt;

&lt;p&gt;We will handle the Firestore queries to fetch the initial dataset and subsequent pages using the &lt;code&gt;MovieService&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Singleton
class MovieService @Inject constructor(
db: FirebaseFirestore
) {
private val movieRef = db.collection("movies")

    fun insertMovieDetails(
        movie: Movie
    ) {
        movieRef.add(movie)
    }

    suspend fun getMovies(
        lastCreatedAt: Long,
        loadMore: Boolean = false
    ): List&amp;lt;Movie&amp;gt; {
        return movieRef
            .whereGreaterThan("createdAt", lastCreatedAt)
            .orderBy("createdAt", Query.Direction.ASCENDING)
            .limit(10) // Limit to 10 items per page
            .get().await().documents.mapNotNull {
                it.toObject(Movie::class.java)
            }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;We will use insertMovieDetails function in the first run of app to setup initial/sample movie data in firestore.&lt;/li&gt;
&lt;li&gt;The getMovies function fetches the next page of movies based on the lastCreatedAt timestamp. It queries Firestore for movies created after the lastCreatedAt value, orders them by createdAt, and limits the results to 10 items per page, making it easy to implement pagination.&lt;/li&gt;
&lt;li&gt;The await() function is an extension function that converts a Task to a suspend function.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Setting Up the ViewModel
&lt;/h2&gt;

&lt;p&gt;The MoviesListViewModel will handle the pagination logic and expose the list of movies to the UI. It will also manage the loading state and trigger data fetching when needed.&lt;/p&gt;

&lt;p&gt;When the app is launched for the first time, we’ll insert some sample movie data into Firestore. This data will be used to demonstrate pagination. And then comment out the insertMovieDetails function.&lt;/p&gt;

&lt;p&gt;Here’s the ViewModel implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@HiltViewModel
class MoviesListViewModel @Inject constructor(
    private val movieService: MovieService
): ViewModel() {
    val moviesList = MutableStateFlow&amp;lt;List&amp;lt;Movie&amp;gt;&amp;gt;(emptyList())
    private val hasMoreMovies = MutableStateFlow(true)
    val showLoader = MutableStateFlow(false)

    // Insert sample movie data into Firestore
    // Comment out this function after the first run
    fun insertMovieData(
        movieList: List&amp;lt;Movie&amp;gt;
    ) = viewModelScope.launch {
        withContext(Dispatchers.IO) {
            movieList.forEach { movie -&amp;gt;
                delay(1000)
                movieService.insertMovieDetails(movie)
                moviesList.value += movie
            }
        }
    }

    // Fetch movie details from Firestore
    // Load more movies if loadMore is true
    fun fetchMovieDetails(
        lastCreatedAt: Long,
        loadMore: Boolean = false
    ) = viewModelScope.launch(Dispatchers.IO) {
        if (loadMore &amp;amp;&amp;amp; !hasMoreMovies.value) return@launch
        showLoader.tryEmit(true) // Show loading indicator
        if (loadMore) delay(3000) // Simulate loading delay
        val movies = movieService.getMovies(lastCreatedAt, loadMore) // Fetch movies
        moviesList.tryEmit((moviesList.value + movies).distinctBy { it.id }) // Update the list of movies with unique items
        hasMoreMovies.tryEmit(movies.isNotEmpty()) // Check if there are more movies to load
        showLoader.tryEmit(false) // Hide loading indicator
    }

    // Load more movies when the user scrolls to the bottom
    // Triggered by the LazyColumn's reachedBottom state
    fun loadMoreMovies() {
        val lastCreatedAt = moviesList.value.last().createdAt
        fetchMovieDetails(lastCreatedAt, loadMore = true)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The insertMovieData function inserts sample movie data into Firestore. This function is used only once to set up the initial dataset.&lt;/li&gt;
&lt;li&gt;The fetchMovieDetails function fetches the next page of movies from Firestore based on the lastCreatedAt timestamp. It updates the moviesList and hasMoreMovies state flows accordingly.&lt;/li&gt;
&lt;li&gt;The loadMoreMovies function is called when the user scrolls to the bottom of the list. It fetches the next page of movies by calling fetchMovieDetails with the lastCreatedAt value of the last movie in the list.&lt;/li&gt;
&lt;li&gt;The showLoader state flow is used to display a loading indicator while fetching data.&lt;/li&gt;
&lt;li&gt;The hasMoreMovies state flow tracks whether there are more movies to load.&lt;/li&gt;
&lt;li&gt;The moviesList state flow holds the list of movies displayed in the UI.&lt;/li&gt;
&lt;li&gt; The distinctBy function ensures that only unique movies are added to the list.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: Building the Composable UI
&lt;/h2&gt;

&lt;p&gt;The UI consists of a &lt;code&gt;LazyColumn&lt;/code&gt; that displays the list of movies and triggers data loading when the user scrolls to the bottom.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Composable
fun MoviesListView(paddingValue: PaddingValues) {
    val viewmodel = hiltViewModel&amp;lt;MoviesListViewModel&amp;gt;()
    val moviesList by viewmodel.moviesList.collectAsState()
    val showLoader by viewmodel.showLoader.collectAsState()

    // Called only once to insert sample movie data on first run
    /*val sampleMovies = remember {
        mutableStateOf(MovieUtils.movies)
    }
    LaunchedEffect(Unit) {
        viewmodel.insertMovieData(sampleMovies.value)
    }*/

    LaunchedEffect(Unit) {
        // Fetch movie details when the screen is launched
        viewmodel.fetchMovieDetails(System.currentTimeMillis())
    }

    val lazyState = rememberLazyListState()

    // Check if the user has scrolled to the bottom of the list
    val reachedBottom by remember {
        derivedStateOf {
            lazyState.reachedBottom() // Custom extension function to check if the user has reached the bottom
        }
    }

    LaunchedEffect(reachedBottom) {
        // Load more movies when the user reaches the bottom of the list and there are more movies to load
        if (reachedBottom &amp;amp;&amp;amp; moviesList.isNotEmpty()) {
            viewmodel.loadMoreMovies()
        }
    }

    LazyColumn(
        state = lazyState,
        modifier = Modifier
            .fillMaxSize()
            .padding(paddingValues = paddingValue)
    ) {
        itemsIndexed(moviesList) { _, movie -&amp;gt;
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
                    .border(
                        width = 1.dp,
                        color = Color.Gray
                    )
            ) {
                MovieCard(movie = movie)
            }
        }

        // Show loading indicator at the end of the list when loading more movies
        item {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
                    .heightIn(min = 20.dp), contentAlignment = Alignment.Center
            ) {
                if (showLoader) {
                    CircularProgressIndicator()
                }
                Spacer(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(20.dp)
                )
            }
        }
    }
}

@Composable
private fun MovieCard(movie: Movie) {
    val imageLoader = LocalContext.current.imageLoader.newBuilder()
        .logger(DebugLogger())
        .build()
    Box(
        modifier = Modifier
            .size(200.dp)
            .background(Color.Black, shape = MaterialTheme.shapes.large)
    ) {
        Image(
            painter = rememberAsyncImagePainter(model = movie.posterUrl, imageLoader = imageLoader),
            contentDescription = null,
            modifier = Modifier
                .fillMaxSize()
        )
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The MoviesListView composable displays the list of movies in a LazyColumn. It fetches the initial dataset when the screen is launched and loads more movies when the user scrolls to the bottom.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;reachedBottom&lt;/strong&gt; extension function checks if the user has scrolled to the bottom of the list. This function is called in a LaunchedEffect block to load more movies when the user reaches the end of the list.&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;fun LazyListState.reachedBottom(): Boolean {
    val visibleItemsInfo = layoutInfo.visibleItemsInfo // Get the visible items
    return if (layoutInfo.totalItemsCount == 0) {
        false // Return false if there are no items
    } else {
        val lastVisibleItem = visibleItemsInfo.last() // Get the last visible item
        val viewportHeight =
            layoutInfo.viewportEndOffset +
                layoutInfo.viewportStartOffset // Calculate the viewport height

        // Check if the last visible item is the last item in the list and fully visible
        // This indicates that the user has scrolled to the bottom
        (lastVisibleItem.index + 1 == layoutInfo.totalItemsCount &amp;amp;&amp;amp;
            lastVisibleItem.offset + lastVisibleItem.size &amp;lt;= viewportHeight)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it! You’ve created a simple yet effective pagination system in Jetpack Compose using Firestore queries and LazyColumn.&lt;/p&gt;




&lt;p&gt;Want to dive deeper into testing this pagination method and uncover its key advantages? &lt;/p&gt;

&lt;p&gt;Head over to the &lt;strong&gt;&lt;a href="https://canopas.com/how-to-create-easy-pagination-in-jetpack-compose-d7e4bb3fc1c6" rel="noopener noreferrer"&gt;full guide on the Canopas blog&lt;/a&gt;&lt;/strong&gt; to get all the details and enhance your Jetpack Compose development skills!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;If you like what you read, be sure to hit 💖 button! — as a writer it means the world!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.&lt;/p&gt;

&lt;p&gt;Happy coding!👋&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>firebase</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Automate Flutter Android App Deployment with GitHub Actions and fastlane</title>
      <dc:creator>Nandani Sharma</dc:creator>
      <pubDate>Tue, 26 Nov 2024 06:40:37 +0000</pubDate>
      <link>https://dev.to/canopassoftware/automate-flutter-android-app-deployment-with-github-actions-and-fastlane-1gei</link>
      <guid>https://dev.to/canopassoftware/automate-flutter-android-app-deployment-with-github-actions-and-fastlane-1gei</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Deploying your Flutter Android app doesn’t have to be a manual, time-consuming process. We’ll guide you through setting up automated deployment using GitHub Actions and Fastlane, enabling you to focus more on building features and less on repetitive tasks. &lt;/p&gt;

&lt;p&gt;Previously, we explored &lt;strong&gt;&lt;a href="https://canopas.com/automate-flutter-ios-app-deployment-with-github-actions-and-codemagic-cli-4d063ea6ef08" rel="noopener noreferrer"&gt;deploying Flutter iOS apps&lt;/a&gt;&lt;/strong&gt;; now, we turn our attention to Android, ensuring a smooth and efficient workflow for app distribution. &lt;/p&gt;

&lt;p&gt;If you’re interested in further customization or adding other deployment tracks (e.g., production, beta), you can modify the Fastfile and GitHub Actions workflow accordingly.&lt;/p&gt;

&lt;p&gt;Now, we’ll set up a Github action for an Android application using fastlane.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why fastlane?
&lt;/h2&gt;

&lt;p&gt;fastlane is a straightforward and efficient tool for integrating CI/CD pipelines into Android app development. It not only simplifies deployment but also handles tasks like generating screenshots, managing code signing, and automating releasing processes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisite
&lt;/h2&gt;

&lt;p&gt;Before diving in, ensure you have a Flutter application and a GitHub repository ready for your project. &lt;/p&gt;

&lt;p&gt;A basic workflow setup is also essential to proceed, including checking out the repository and setting up the Flutter environment. Since this setup is covered in our previous article, we won’t repeat it here.&lt;/p&gt;

&lt;p&gt;For detailed instructions on setting up the workflow and Flutter environment, refer to our &lt;strong&gt;&lt;a href="https://canopas.com/automate-flutter-ios-app-deployment-with-github-actions-and-codemagic-cli-4d063ea6ef08" rel="noopener noreferrer"&gt;Basic Workflow Setup Guide&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s Get Started
&lt;/h2&gt;

&lt;p&gt;We’ll break down the auto-deployment process into three key parts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure Android Code Signing&lt;/li&gt;
&lt;li&gt;Set Up fastlane&lt;/li&gt;
&lt;li&gt;Add Jobs for Publishing the App&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Configure Android Code Signing
&lt;/h2&gt;

&lt;p&gt;To publish your app on the Play Store, it must be signed with a digital certificate. Android uses two signing keys for this process: Upload key and App signing key. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are these keys, and why are they important?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Upload Key
&lt;/h3&gt;

&lt;p&gt;The Upload Key is used to sign your app when you upload it to the Play Console. After uploading, Google Play will re-sign your app with the App Signing Key before distributing it to users.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Upload Key is essential for authenticating your app during uploads.&lt;/li&gt;
&lt;li&gt;It needs to be kept secure to prevent unauthorized access and to protect the integrity of your app’s updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To generate the Upload Key, follow the official Android Developer Guide on App Signing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While creating the Upload Key, make sure to remember the password and key alias that you set for the key. You’ll need these later when configuring Fastlane and setting up the deployment process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  App Signing Key
&lt;/h3&gt;

&lt;p&gt;The App Signing Key is the primary key used by Google Play to sign your app before delivering it to users. This key ensures that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your app updates are trusted by users.&lt;/li&gt;
&lt;li&gt;Only apps signed with the same key can be installed or updated.&lt;/li&gt;
&lt;li&gt;This key remains consistent throughout the lifetime of your app, providing security for future updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To generate the App Signing Key, follow the step-by-step instructions in the &lt;a href="https://docs.fastlane.tools/getting-started/android/setup/" rel="noopener noreferrer"&gt;Collect your Google credentials&lt;/a&gt; section in the Fastlane setup documentation guide.&lt;/p&gt;

&lt;p&gt;When creating the upload key, a JSON file is generated and downloaded. This file contains essential credentials that you will need to authenticate and manage your app on the Play Console. It will be required in the later steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Set Up Fastlane
&lt;/h2&gt;

&lt;p&gt;Now, that we have the Upload Key and App Signing Key ready, it’s time to set up fastlane.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install fastlane
&lt;/h3&gt;

&lt;p&gt;To get started, you’ll need to install Fastlane on your machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Using RubyGems(macOS/Linux/Windows)
sudo gem install fastlane

# Alternatively using Homebrew(macOS)
brew install fastlane
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set Up fastlane in Your Project
&lt;/h3&gt;

&lt;p&gt;fastlane is installed, let’s configure it within your Flutter project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initialize fastlane
&lt;/h3&gt;

&lt;p&gt;Open your terminal and navigate to your project’s root directory and change the directory to android. Then, run the following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fastlane init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the setup process, you’ll be prompted with a series of questions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Package Name:&lt;/strong&gt; Provide the package name of your application. You can find it in your &lt;code&gt;android/app/build.gradle&lt;/code&gt; file, under &lt;code&gt;defaultConfig &amp;gt; applicationId&lt;/code&gt;. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;applicationId "io.example.yourapp"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Path to JSON Secret File:&lt;/strong&gt; Press Enter when asked for the path to your JSON secret file. We'll set up it later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download existing metadata and set up metadata management:&lt;/strong&gt; Choose the option based on how you plan to manage app metadata and screenshots during deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Play Upload:&lt;/strong&gt; When asked if you plan on uploading information to Google Play via fastlane, answer ’n’ (No). We will configure this in a later step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, you can see a newly created &lt;code&gt;./fastlane&lt;/code&gt; directory in your project. Inside this directory, you’ll find two key files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Appfile:&lt;/strong&gt; This file contains global configuration information for your app, such as the app’s package name and JSON key file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fastfile:&lt;/strong&gt; This file defines the “lanes,” which are sets of actions that drive the behavior of fastlane for various tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, open the &lt;strong&gt;Appfile&lt;/strong&gt; and add the following line to specify the path to your JSON key file, which will be used for authenticating with the Google Play API.&lt;/p&gt;

&lt;p&gt;Also, ensure that the &lt;strong&gt;package_name&lt;/strong&gt; is set to the correct value for your app and set the JSON file path in the &lt;code&gt;Appfile&lt;/code&gt; as follows&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;json_key_file("google_play_api_key.json") # Path to the json secret file
package_name("com.exapmle.appname")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🤔 Don’t worry!! We will add the &lt;code&gt;google_play_api_key.json&lt;/code&gt; file in the next steps. Stay tuned!&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Secrets to the Repository
&lt;/h3&gt;

&lt;p&gt;In this step, we’ll add all the necessary environment variables and secrets that fastlane and the app will use during deployment.&lt;/p&gt;

&lt;p&gt;To add new secrets and variables to your repository, go to &lt;strong&gt;Settings &amp;gt; Secrets and Variables.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ftbyjf2anp7s6imtm26wh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftbyjf2anp7s6imtm26wh.png" alt="Github Action Secrets" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;APKSIGN_KEYSTORE_PASS:&lt;/strong&gt; The password for the keystore that is set when creating the &lt;code&gt;development.jks&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;APKSIGN_KEY_ALIAS:&lt;/strong&gt; The alias created for the key inside the keystore .&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;APKSIGN_KEY_PASS:&lt;/strong&gt; The password associated with the alias created for app’s signing key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;APKSIGN_KEYSTORE_BASE64:&lt;/strong&gt; We need to convert the &lt;code&gt;development.jks&lt;/code&gt; keystore file, which is generated during the creation of the App Signing Key, into Base64 format to store it as a secret. For that, open the terminal and navigate to the directory where the &lt;code&gt;development.jks&lt;/code&gt; file is located.
&lt;code&gt;base64 -i &amp;lt;File name&amp;gt;| pbcopy&lt;/code&gt;
Now that the Keystore is copied to your clipboard, paste this Base64 content as the value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;APP_PLAY_SERVICE_JSON_BASE64:&lt;/strong&gt; Convert JSON file downloaded while creating Upload key to Base64 format in a similar way as the keystore file and add it as secret.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Set up environment variables
&lt;/h3&gt;

&lt;p&gt;We will set up the environment variables for the deployment job. These variables will reference the secrets you added to your GitHub repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  android_deployment:
    runs-on: ubuntu-latest
    env:
      APP_PLAY_SERVICE_JSON: ${{ secrets.APP_PLAY_SERVICE_JSON_BASE64 }}
      APKSIGN_KEYSTORE_BASE64: ${{ secrets.APKSIGN_KEYSTORE_BASE64 }}
      APKSIGN_KEYSTORE_PASS: ${{ secrets.APKSIGN_KEYSTORE_PASS }}
      APKSIGN_KEY_ALIAS: ${{ secrets.APKSIGN_KEY_ALIAS }}
      APKSIGN_KEY_PASS: ${{ secrets.APKSIGN_KEY_PASS }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure &lt;code&gt;build.gradle&lt;/code&gt; for Signing
&lt;/h3&gt;

&lt;p&gt;To enable the Android build system to use these environment variables during the build process, add the following configuration to your &lt;code&gt;android/app/build.gradle&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signingConfigs {
    if (System.getenv("APKSIGN_KEYSTORE") != null) {
        release {
            storeFile file(System.getenv("APKSIGN_KEYSTORE"))
            storePassword System.getenv("APKSIGN_KEYSTORE_PASS")
            keyAlias System.getenv("APKSIGN_KEY_ALIAS")
            keyPassword System.getenv("APKSIGN_KEY_PASS")
        }
    } else {
        release {
            // Signing with the debug keys for now, so `flutter run --release` works.
            // Generate Debug keystore and add it here
        }
    }
}

 buildTypes {
            release {
            minifyEnabled true
            debuggable false
            shrinkResources true

            signingConfig signingConfigs.release
        }
        debug {
            minifyEnabled true
            debuggable true

            signingConfig signingConfigs.release
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever we initiate a release build, the system will look for the &lt;strong&gt;Keystore&lt;/strong&gt; in the specified environment variables and use them to sign the build with the provided &lt;strong&gt;Keystore&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For local testing in release mode, you should generate a debug Keystore. The app will use this debug keystore if the release keystore is not available.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We've discussed further steps to define jobs for distribution of the Android app in our &lt;strong&gt;&lt;a href="https://canopas.com/automate-flutter-android-app-deployment-with-git-hub-actions-and-fastlane" rel="noopener noreferrer"&gt;complete guide&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To read the step-by-step setup &amp;amp; implementation, check out our complete guide on &lt;strong&gt;&lt;a href="https://canopas.com/automate-flutter-android-app-deployment-with-git-hub-actions-and-fastlane" rel="noopener noreferrer"&gt;Automate Flutter Android App Deployment with GitHub Actions and fastlane&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;If you like what you read, be sure to hit 💖 button! — as a writer it means the world!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.&lt;/p&gt;

&lt;p&gt;Happy coding!👋&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>githubactions</category>
      <category>android</category>
      <category>programming</category>
    </item>
    <item>
      <title>Golang + htmx + Tailwind CSS: Create a Responsive Web Application</title>
      <dc:creator>Nandani Sharma</dc:creator>
      <pubDate>Tue, 19 Nov 2024 12:03:39 +0000</pubDate>
      <link>https://dev.to/canopassoftware/golang-htmx-tailwind-css-create-a-responsive-web-application-2hfl</link>
      <guid>https://dev.to/canopassoftware/golang-htmx-tailwind-css-create-a-responsive-web-application-2hfl</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;In today’s web development landscape, JavaScript has long been the language of choice for creating dynamic and interactive web applications.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As a Go developer, what if you don’t want to use Javascript and still implement a responsive web application?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine a sleek to-do list app that updates instantly as you check off tasks without a full-page reload. This is the power of Golang and htmx!&lt;/p&gt;

&lt;p&gt;Combining Go and htmx allows us to create responsive and interactive web applications without writing a single line of JavaScript.&lt;/p&gt;

&lt;p&gt;In this blog, we will explore how to use htmx and Golang to build web applications. (It can be used with other your favorite platforms, too.) &lt;/p&gt;

&lt;p&gt;As a learning, we will implement basic create and delete operations for users.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is htmx?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://htmx.org/" rel="noopener noreferrer"&gt;htmx&lt;/a&gt; is a modern HTML extension that adds bidirectional communication between the browser and the server.&lt;/p&gt;

&lt;p&gt;It allows us to create dynamic web pages without writing JavaScript, as it provides access to AJAX, server-sent events, etc in HTML directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How htmx works?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When a user interacts with an element that has an htmx attribute (e.g., clicks a button), the browser triggers the specified event.&lt;/li&gt;
&lt;li&gt;htmx intercepts the event and sends an HTTP request to the server-side endpoint specified in the attribute (e.g., &lt;code&gt;hx-get="/my-endpoint"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The server-side endpoint processes the request and generates an HTML response.&lt;/li&gt;
&lt;li&gt;htmx receives the response and updates the DOM according to the &lt;code&gt;hx-target&lt;/code&gt; and &lt;code&gt;hx-swap&lt;/code&gt; attributes. This can involve:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; — Replacing the entire element’s content.&lt;br&gt;
 — Inserting new content before or after the element.&lt;br&gt;
 — Appending content to the end of the element.&lt;/p&gt;

&lt;p&gt;Let’s understand it in more depth with an 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;button hx-get="/fetch-data" hx-target="#data-container"&amp;gt;
   Fetch Data
&amp;lt;/button&amp;gt;
&amp;lt;div id="data-container"&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, when the button is clicked:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;htmx sends a GET request to &lt;code&gt;/fetch-data.&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The server-side endpoint fetches data and renders it as HTML.&lt;/li&gt;
&lt;li&gt;The response is inserted into the &lt;code&gt;#data-container&lt;/code&gt; element.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Create and delete the user
&lt;/h3&gt;

&lt;p&gt;Below are the required tools/frameworks to build this basic app.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gin (Go framework)&lt;/li&gt;
&lt;li&gt;Tailwind CSS &lt;/li&gt;
&lt;li&gt;htmx&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Basic setup&lt;/strong&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create main.go file at the root directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;main.go&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
 "fmt"
 "github.com/gin-gonic/gin"
)

func main() {
 router := gin.Default()

 router.Run(":8080")
 fmt.Println("Server is running on port 8080")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;It sets up a basic Go server, running at port 8080. &lt;br&gt;
Run go run main.go to run the application.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Create a HTML file at the root directory, to render the user list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;users.html&lt;/strong&gt;&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;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
   &amp;lt;head&amp;gt;
      &amp;lt;title&amp;gt;Go + htmx app &amp;lt;/title&amp;gt;
      &amp;lt;script src="https://unpkg.com/htmx.org@2.0.0" integrity="sha384-wS5l5IKJBvK6sPTKa2WZ1js3d947pvWXbPJ1OmWfEuxLgeHcEbjUUA5i9V5ZkpCw" crossorigin="anonymous"&amp;gt;&amp;lt;/script&amp;gt;
      &amp;lt;script src="https://cdn.tailwindcss.com"&amp;gt;&amp;lt;/script&amp;gt;
   &amp;lt;/head&amp;gt;
   &amp;lt;body class="text-center flex flex-col w-full gap-6 mt-10"&amp;gt;
      &amp;lt;table id="user-list" class="w-1/2 mx-auto mt-4 border border-gray-300"&amp;gt;
         &amp;lt;thead&amp;gt;
            &amp;lt;tr class="border border-gray-300"&amp;gt;
               &amp;lt;th class="px-4 py-2"&amp;gt;Name&amp;lt;/th&amp;gt;
               &amp;lt;th class="px-4 py-2"&amp;gt;Email&amp;lt;/th&amp;gt;
               &amp;lt;th class="px-4 py-2"&amp;gt;Actions&amp;lt;/th&amp;gt;
            &amp;lt;/tr&amp;gt;
         &amp;lt;/thead&amp;gt;
         &amp;lt;tbody&amp;gt;
            {{ range .users }}
            &amp;lt;tr class="border border-gray-300"&amp;gt;
               &amp;lt;td class="px-4 py-2"&amp;gt;{{ .Name }}&amp;lt;/td&amp;gt;
               &amp;lt;td class="px-4 py-2"&amp;gt;{{ .Email }}&amp;lt;/td&amp;gt;
               &amp;lt;td class="px-4 py-2"&amp;gt;
                  &amp;lt;button class="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-2 rounded"&amp;gt;Delete&amp;lt;/button&amp;gt;
               &amp;lt;/td&amp;gt;
            &amp;lt;/tr&amp;gt;
            {{ end }}
         &amp;lt;/tbody&amp;gt;
      &amp;lt;/table&amp;gt;
   &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We have included,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;htmx&lt;/strong&gt; using the script tag — &lt;u&gt;&lt;a href="https://unpkg.com/htmx.org@2.0.0" rel="noopener noreferrer"&gt;https://unpkg.com/htmx.org@2.0.0&lt;/a&gt;&lt;/u&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tailwind CSS&lt;/strong&gt; with cdn link —&lt;br&gt;
&lt;u&gt;&lt;a href="https://cdn.tailwindcss.com" rel="noopener noreferrer"&gt;https://cdn.tailwindcss.com&lt;/a&gt;&lt;/u&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now, we can use Tailwind CSS classes and render the templates with htmx.&lt;/p&gt;

&lt;p&gt;As we see in &lt;code&gt;users.html&lt;/code&gt;, we need to pass users array to the template, so that it can render the users list. &lt;/p&gt;

&lt;p&gt;For that let’s create a hardcoded static list of users and create a route to render &lt;code&gt;users.html&lt;/code&gt; .&lt;/p&gt;

&lt;h3&gt;
  
  
  Fetch users
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;main.go&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
 "fmt"
 "net/http"
 "text/template"

 "github.com/gin-gonic/gin"
)

func main() {
 router := gin.Default()

 router.GET("/", func(c *gin.Context) {
  users := GetUsers()

  tmpl := template.Must(template.ParseFiles("users.html"))
  err := tmpl.Execute(c.Writer, gin.H{"users": users})
    if err != nil {
       panic(err)
    }
 })

 router.Run(":8080")
 fmt.Println("Server is running on port 8080")
}

type User struct {
 Name  string
 Email string
}

func GetUsers() []User {
 return []User{
  {Name: "John Doe", Email: "johndoe@example.com"},
  {Name: "Alice Smith", Email: "alicesmith@example.com"},
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;We have added a route / to render the user list and provide a static list of users (to which we will add new users ahead).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s all. Restart the server and let’s visit — &lt;strong&gt;&lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;http://localhost:8080/&lt;/a&gt;&lt;/strong&gt; to check whether it renders the user list or not. It will render the user list as below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fh5fwowmyj1tu14s6tgjs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fh5fwowmyj1tu14s6tgjs.png" alt="Rendered users list" width="800" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create user
&lt;/h3&gt;

&lt;p&gt;Create file &lt;strong&gt;user_row.html&lt;/strong&gt;. It will be responsible for adding a new user row to the user table.&lt;/p&gt;

&lt;h4&gt;
  
  
  user_row.html
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;tr class="border border-gray-300"&amp;gt;
    &amp;lt;td class="px-4 py-2"&amp;gt;{{ .Name }}&amp;lt;/td&amp;gt;
    &amp;lt;td class="px-4 py-2"&amp;gt;{{ .Email }}&amp;lt;/td&amp;gt;
    &amp;lt;td class="px-4 py-2"&amp;gt;
        &amp;lt;button class="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-2 rounded"&amp;gt;Delete&amp;lt;/button&amp;gt;
    &amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;P.S. — You will notice that it has the same structure as the user table row in &lt;code&gt;users.html&lt;/code&gt; . That’s because we want the same styling for the new row we are going to add.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modify users.html. Add the Below code above the &lt;code&gt;&amp;lt;table&amp;gt;&amp;lt;/table&amp;gt;&lt;/code&gt; tags.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form hx-post="/users" hx-target="#user-list" hx-swap="beforeend"&amp;gt;
   &amp;lt;input type="text" name="name" placeholder="Name" class="border border-gray-300 p-2 rounded"&amp;gt;
   &amp;lt;input type="email" name="email" placeholder="Email" class="border border-gray-300 p-2 rounded"&amp;gt;
   &amp;lt;button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"&amp;gt;Add User&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;hx-post=“/users”&lt;/strong&gt; — When the form will be submitted, it will trigger post request to /users route.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;hx-target=“#user-list”&lt;/strong&gt; —Specify the target where we want to add data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;hx-swap=“beforeend”&lt;/strong&gt;  — Specify the position where want to add data. In our case we want to add new user at the end of list, so we have used &lt;strong&gt;beforeend&lt;/strong&gt; value.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s implement &lt;code&gt;/users&lt;/code&gt;(POST) route in the main.go file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router.POST("/users", func(c *gin.Context) {
  tmpl := template.Must(template.ParseFiles("user_row.html"))

  name := c.PostForm("name")
  email := c.PostForm("email")

  user := User{Name: name, Email: email}
  err := tmpl.Execute(c.Writer, user)
  if err != nil {
   panic(err)
  }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;It takes the &lt;strong&gt;name&lt;/strong&gt; and &lt;strong&gt;email&lt;/strong&gt; from the form input and executes the &lt;strong&gt;user_row.html&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s try to add a new user to the table. Visit &lt;u&gt;&lt;strong&gt;&lt;a href="http://localhost:8080/" rel="noopener noreferrer"&gt;http://localhost:8080/&lt;/a&gt;&lt;/strong&gt;&lt;/u&gt; and click the &lt;strong&gt;Add User&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fg8q4cogzqkypn0dxz703.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fg8q4cogzqkypn0dxz703.gif" alt="Add new user to the list" width="1647" height="870"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yayy! We’ve successfully added a new user to the list 🎉.&lt;/p&gt;

&lt;p&gt;To dive deeper into the detail implementation guide, check out the complete guide at &lt;strong&gt;&lt;a href="https://canopas.com/golang-htmx-tailwind-css-create-responsive-web-application" rel="noopener noreferrer"&gt;Canopas&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;If you like what you read, be sure to hit 💖 button! — as a writer it means the world!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.&lt;/p&gt;

&lt;p&gt;Happy coding!👋&lt;/p&gt;

</description>
      <category>go</category>
      <category>webdev</category>
      <category>css</category>
      <category>html</category>
    </item>
    <item>
      <title>Integrating Live Activity and Dynamic Island in iOS: A Complete Guide</title>
      <dc:creator>Nandani Sharma</dc:creator>
      <pubDate>Fri, 15 Nov 2024 07:20:57 +0000</pubDate>
      <link>https://dev.to/canopassoftware/integrating-live-activity-and-dynamic-island-in-ios-a-complete-guide-4i78</link>
      <guid>https://dev.to/canopassoftware/integrating-live-activity-and-dynamic-island-in-ios-a-complete-guide-4i78</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;With the release of &lt;strong&gt;iOS 16&lt;/strong&gt;, Apple introduced &lt;strong&gt;Live Activities&lt;/strong&gt;, and later with &lt;strong&gt;iPhone 14 Pro&lt;/strong&gt;, the &lt;strong&gt;Dynamic Island&lt;/strong&gt;—two powerful tools that allow us to present real-time, glanceable updates directly on the Lock Screen and at the top of the screen on the Dynamic Island. These features are designed to keep users informed about ongoing activities, like delivery tracking, live sports scores, or wait times, without requiring them to unlock their devices or open the app.&lt;/p&gt;

&lt;p&gt;In this two-part guide, we’ll discuss everything you need to know to integrate Live Activities and Dynamic Island effectively in your iOS app. We'll detail each step from understanding design constraints to setting up a Live Activity, handling updates, and adding interactions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we're going to cover in this first part?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Are Live Activities and Dynamic Island?&lt;/li&gt;
&lt;li&gt;Live Activity presentations and constraints&lt;/li&gt;
&lt;li&gt;Design layout for different presentations&lt;/li&gt;
&lt;li&gt;Start, update and end the activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Here's what will achieve at the end of this blog.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0m68k9ms0h80pgmcn8yv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0m68k9ms0h80pgmcn8yv.gif" alt="Integrating Live Activity and Dynamic Island in iOS" width="268" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What Are Live Activities and Dynamic Island?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Live Activities&lt;/strong&gt; allow apps to display real-time information on the Lock Screen. These updates are perfect for tracking time-sensitive events like food delivery, ride-sharing ETAs, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamic Island&lt;/strong&gt; extends this functionality by offering a compact, interactive display area. Users can see and interact with live updates at the top of their screens while using other apps, creating a nondisturbing way to stay informed on background activities like calls, music, and live events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Live Activity presentations and constraints
&lt;/h2&gt;

&lt;p&gt;Before implementing Live Activities and Dynamic Island, it’s important to understand the design guidelines and presentation options available. Apple has specific constraints and recommendations to ensure that these features offer a consistent, user-friendly experience across apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Presentations for Live Activities
&lt;/h3&gt;

&lt;p&gt;Live Activities offers different presentation styles on the Lock Screen and Dynamic Island. Understanding these styles is crucial to creating a well-designed Live Activity that feels native to iOS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lock Screen Presentation
&lt;/h3&gt;

&lt;p&gt;On the Lock Screen, Live Activities appear as banners with detailed information. They provide a flexible area for displaying key data about your app’s real-time events, like estimated arrival, or live score updates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Island Presentations
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Compact Presentation
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;compact view&lt;/strong&gt; is the most commonly seen view, typically displayed when the device is not actively engaged with ongoing tasks. It consists of two separate elements positioned on either side of the TrueDepth camera.&lt;/p&gt;

&lt;p&gt;To ensure clarity, use consistent colors and typography for visibility, and avoid adding padding between the content and the camera to prevent misalignment.&lt;/p&gt;

&lt;p&gt;Make sure that tapping on either the leading or trailing content opens the same scene in your app to provide a consistent and intuitive user experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fa1gcoyn5xoxizeunygwb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fa1gcoyn5xoxizeunygwb.png" alt="Compact Presentation" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Minimal Presentation
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;minimal presentation&lt;/strong&gt; is used when multiple Live Activities are active simultaneously. In this state, your Live Activity is displayed as a small circle or oval, either on the Dynamic Island or detached from it. This presentation should focus on delivering the most essential and up-to-date information.&lt;/p&gt;

&lt;p&gt;Instead of a logo, display live, dynamic content like a countdown or real-time update of the essential information, even if it means abbreviating details. When users tap on the minimal display, it should open the app to the relevant event or task for more details. If the users touch and hold the minimal Live Activity, users can access essential controls or view more content in the expanded presentation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fjucokajygnadcoy7xc0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjucokajygnadcoy7xc0w.png" alt="Minimal Presentation" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Expanded Presentation
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;expanded presentation&lt;/strong&gt; appears when users tap and hold a compact or minimal Live Activity. This view gives more space for detailed information and interactions, while still keeping the smooth, rounded "capsule" shape.&lt;/p&gt;

&lt;p&gt;Make sure the content expands in a way that feels natural and doesn’t cause confusion. If you need more vertical space, keep the edges curved to maintain a clean look. Avoid making the height awkward or uneven. Like the other presentations, keep your content aligned around the camera area to maintain a tidy and efficient layout.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fhrflf52ytikhurv7fx7o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fhrflf52ytikhurv7fx7o.png" alt="Expanded Presentation" width="800" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Constraints
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avoid Overcrowding:&lt;/strong&gt; Don’t overcrowd the Live Activity with too much information. Keep it concise and focus on the most relevant data to ensure clarity and a good user experience.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Content Alignment:&lt;/strong&gt; When designing Live Activities, make sure content is properly aligned and visually balanced around the camera area (in the compact and expanded presentations) to avoid misalignment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Image Asset Size:&lt;/strong&gt; The image used in a Live Activity must fit within the size limits of the presentation for the device. If the image is too large, the system may not start the Live Activity. For example, an image for the minimal presentation should not exceed 45x36.67 points.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Duration of Live Activity:&lt;/strong&gt; A Live Activity can be active for up to 8 hours. After this, the system automatically ends it and removes it from the Dynamic Island. However, it will stay on the Lock Screen for up to 4 more hours, or until manually removed, whichever happens first. This means a Live Activity can remain on the Lock Screen for a maximum of 12 hours.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Live Activity Sandbox:&lt;/strong&gt; Each Live Activity runs in its own sandbox. Unlike widgets, it can't access the network or receive location updates. If you need to update the data, use ActivityKit in your app or allow the Live Activity to receive push notifications through ActivityKit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Minimal Distractions:&lt;/strong&gt; Since Live Activities are meant to provide quick, glanceable information, avoid cluttering them with non-essential details. Stick to only the most important and up-to-date data that users need to see at a glance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data size limit:&lt;/strong&gt; The combined size of static and dynamic data for a Live Activity, including data for ActivityKit updates and ActivityKit push notifications, cannot exceed &lt;strong&gt;4 KB&lt;/strong&gt;. Keep the data within this limit to ensure proper functionality and smooth performance of your Live Activity.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Integrating Live Activity and Dynamic Island in iOS can greatly enhance the user experience by providing real-time updates and immersive interactions directly on the iPhone’s home screen.&lt;/p&gt;

&lt;p&gt;To explore the full guide on seamlessly integrating these features, check out our complete blog at &lt;strong&gt;&lt;a href="https://canopas.com/integrating-live-activity-and-dynamic-island-in-i-os-a-complete-guide" rel="noopener noreferrer"&gt;Canopas&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;If you like what you read, be sure to hit 💖 button! — as a writer it means the world!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.&lt;/p&gt;

&lt;p&gt;Happy coding!👋&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building a Better Monorepo with TypeScript, Turborepo, or Nx</title>
      <dc:creator>Nandani Sharma</dc:creator>
      <pubDate>Mon, 11 Nov 2024 10:29:38 +0000</pubDate>
      <link>https://dev.to/canopassoftware/building-a-better-monorepo-with-typescript-turborepo-or-nx-32ij</link>
      <guid>https://dev.to/canopassoftware/building-a-better-monorepo-with-typescript-turborepo-or-nx-32ij</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Monorepo is a software development strategy that creates shared libraries and dependencies in a single repository. Monorepos offers a solution for crucial dependency management by centralizing codebases, improving collaboration, and streamlining dependency management. &lt;/p&gt;

&lt;p&gt;This article aims to explain the process of creating monorepos using well-known tools and technologies like TypeScript, Turborepo, and Nx, allowing you to choose the best tool based on your requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Monorepo?
&lt;/h2&gt;

&lt;p&gt;Imagine managing three separate applications for a project: a Web App, a Mobile App, and an Admin Panel. Each resides in its git repository and shares a common UI component library. Now think, when you need to update a critical button component for accessibility.&lt;/p&gt;

&lt;p&gt;The process becomes a headache:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update the component library and publish to NPM&lt;/li&gt;
&lt;li&gt;Update each application one by one&lt;/li&gt;
&lt;li&gt;Deal with version conflicts and dependencies&lt;/li&gt;
&lt;li&gt;Ensure consistency across all projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sounds frustrating, right? This is where monorepos comes in—a simpler approach where all your projects live under one roof, making it easier to coordinate changes and maintain consistency across your codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Let’s start with monorepos…&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In the following sections, we will create basic utils monorepo which can be used anywhere in the project. &lt;/p&gt;

&lt;h2&gt;
  
  
  Monorepo with Typescript
&lt;/h2&gt;

&lt;p&gt;In this section, we will build a monorepo manually using just TypeScript and npm.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initialize the Project
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a monorepo example directory and set the root project using the below commands,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init -y
npm install typescript
npm install --save-dev @types/node ts-node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Organize Packages
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;packages&lt;/code&gt; folder and a &lt;code&gt;utils&lt;/code&gt; directory within it to act as a shared library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p packages/utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Init npm inside utils with scope &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/monorepo"&gt;@monorepo&lt;/a&gt;&lt;/strong&gt;,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init --scope @monorepo --workspace ./packages/utils -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;packages/utils/package.json&lt;/code&gt; will look like this,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "@monorepo/utils",
  "version": "1.0.0",
  "main": "index.js",
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" &amp;amp;&amp;amp; exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will also add a workspace field in the root’s package.json like below. If you have multiple monorepos, you can update &lt;code&gt;workspace&lt;/code&gt; using wildcards like "packages/*".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"workspaces": [
    "packages/utils" or "packages/*"
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure TypeScript
&lt;/h3&gt;

&lt;p&gt;We need to configure typescript in both the directory &lt;code&gt;root&lt;/code&gt; and &lt;code&gt;packages&lt;/code&gt;,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx tsc --init &amp;amp;&amp;amp; cd packages/utils &amp;amp;&amp;amp; npx tsc --init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can update baseDir and rootDir in &lt;code&gt;packages/utils’s tsconfig.jsonas&lt;/code&gt; needed. In my case it is,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"rootDir": "./src",
"outDir": "./dist", // make sure to update main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: Make sure to update the &lt;strong&gt;main&lt;/strong&gt; field in the package.json based on your &lt;strong&gt;outDir.&lt;/strong&gt; For me, it is &lt;strong&gt;dist/index.js&lt;/strong&gt; .&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;As we need monorepo to be referenced in other projects, we need to add composite: true in &lt;code&gt;utils/tsconfig.json&lt;/code&gt; .&lt;/li&gt;
&lt;li&gt;Add utils reference in the project &lt;code&gt;root’s tscofig.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"compilerOptions": {...},
"references": [
     {"path": "./packages/utils" },
 ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For module resolution and path mapping, we will add the following in the root’s tsconfig.json&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"compilerOptions": {
  ...,
  "baseUrl": "./",              
  "paths": {
      "@monorepo/utiltest": ["packages/utils/*"],
   }
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add and Build Code
&lt;/h3&gt;

&lt;p&gt;Write a sample function in &lt;code&gt;packages/utils/src/index.ts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// packages/utils/src/index.ts

export function multiply(a: number, b: number): number {
    return a * b;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;"build": "tsc --build"&lt;/code&gt; in the root and utils &lt;code&gt;package.json&lt;/code&gt; scripts, then build with &lt;code&gt;npm run build&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Import &lt;a class="mentioned-user" href="https://dev.to/monorepo"&gt;@monorepo&lt;/a&gt;/utils into the root file to verify the function works as expected.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @monorepo/utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;You can use the monorepo package like below,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { multiply } from "@monorepo/utils";

const result = multiply(2, 3);
console.log(`result: ${result}`);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yeah, You have created a basic monorepo setup. You can build as many as monorepos and use them like this. &lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use?
&lt;/h3&gt;

&lt;p&gt;Monorepo’s with only typescript is useful for small, simple projects or experimental purposes without requiring third-party tooling.&lt;/p&gt;

&lt;p&gt;Next, let’s create monorepo with &lt;a href="https://github.com/vercel/turborepo" rel="noopener noreferrer"&gt;Turborepo&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monorepo with Turborepo
&lt;/h2&gt;

&lt;p&gt;Turborepo is a high-performance monorepo build tool that simplifies dependency management and speeds up builds through caching.&lt;/p&gt;

&lt;p&gt;Follow the below steps to configure monorepo using Turborepo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set Up Turborepo
&lt;/h3&gt;

&lt;p&gt;Install Turborepo as a dev dependency,&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 turbo -D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Add packageManager to each package.json to ensure compatibility with Turborepo.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"packageManager": "npm@&amp;lt;npm-version&amp;gt;",
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure Turborepo
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;turbo.json&lt;/code&gt; in the root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "$schema": "https://turborepo.org/schema.json",
    "tasks": {
      "build": {
        "dependsOn": ["^build"],
        "outputs": ["dist/**"]
      },
      "dev": {
        "cache": false
      }
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;tasks&lt;/code&gt; section defines tasks like &lt;code&gt;build&lt;/code&gt; dependencies and caching behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add and Build Code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add a &lt;code&gt;data-transform&lt;/code&gt; package to illustrate Turborepo's dependency management.&lt;/li&gt;
&lt;li&gt;Add code for transforming data in &lt;code&gt;data-transform&lt;/code&gt;,
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export function transformData(data: any): any {   
   return data.map((item) =&amp;gt; ({ ...item, transformed: true })); 
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build the entire project using,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx turbo run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Turborepo automatically caches results, speeding up subsequent builds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;You can install &lt;code&gt;@monorepo/data-transform&lt;/code&gt; into the &lt;strong&gt;root&lt;/strong&gt; or &lt;strong&gt;utils&lt;/strong&gt; package, and verify it's working as expected.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use?
&lt;/h3&gt;

&lt;p&gt;Ideal for mid-size projects that need optimized build times and clear dependency management.&lt;/p&gt;




&lt;p&gt;Yeah! We've Successfully Built a Monorepo with TypeScript and Turborepo!&lt;/p&gt;

&lt;p&gt;Ready to take it a step further with Nx? 🚀 &lt;/p&gt;

&lt;p&gt;Head over to our &lt;strong&gt;&lt;a href="https://canopas.com/building-better-monorepo-with-type-script-turborepo-or-nx" rel="noopener noreferrer"&gt;full blog&lt;/a&gt;&lt;/strong&gt; to explore how to implement &lt;strong&gt;Monorepo with Nx&lt;/strong&gt; and discover even more insights on effective monorepo management.&lt;/p&gt;




&lt;p&gt;If you like what you read, be sure to  hit 💖 button! — as a writer it means the world!&lt;/p&gt;

&lt;p&gt;I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.&lt;/p&gt;

&lt;p&gt;Happy coding! 👋&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>programming</category>
      <category>npm</category>
    </item>
    <item>
      <title>Automate Flutter iOS App Deployment with GitHub Actions and Codemagic CLI</title>
      <dc:creator>Nandani Sharma</dc:creator>
      <pubDate>Wed, 06 Nov 2024 05:54:59 +0000</pubDate>
      <link>https://dev.to/canopassoftware/automate-flutter-ios-app-deployment-with-github-actions-and-codemagic-cli-1ibd</link>
      <guid>https://dev.to/canopassoftware/automate-flutter-ios-app-deployment-with-github-actions-and-codemagic-cli-1ibd</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we’ll set up an automated deployment process for a Flutter iOS application using GitHub Actions. You’ll learn how to create a workflow that runs Codemagic CLI tools to build your app and upload it to TestFlight.&lt;/p&gt;

&lt;p&gt;By the end, you’ll have an efficient app distribution pipeline that saves time and effort, allowing you to focus more on development and less on manual deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Codemagic?
&lt;/h2&gt;

&lt;p&gt;Codemagic CLI tools are a free, open-source set of command-line utilities that power Codemagic’s CI/CD service.&lt;/p&gt;

&lt;p&gt;When I first tried setting up auto-deployment for my iOS app, I faced several frustrating and hard-to-resolve pipeline failures. That’s when I discovered Codemagic CLI — it required no configuration and simplified the entire build automation process.&lt;/p&gt;

&lt;p&gt;Since then, I’ve been happily using it! 😅&lt;/p&gt;

&lt;p&gt;One of the standout advantages of Codemagic CLI tools is their cross-platform compatibility. Whether you’re building for iOS or Android, you don’t need separate tools for each platform, making it a versatile choice for mobile developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you begin, ensure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An App Store account enrolled in the Apple Developer Program.&lt;/li&gt;
&lt;li&gt;A Flutter application ready for deployment.&lt;/li&gt;
&lt;li&gt;A GitHub repository is set up for your project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this tutorial, I’ll be using the Flutter counter app as an example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s get started! 👇
&lt;/h2&gt;

&lt;p&gt;To make it easier, I have divided the article into 3 separate sections.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic Workflow Setup&lt;/li&gt;
&lt;li&gt;Setting things up in App Store Connect&lt;/li&gt;
&lt;li&gt;Add Jobs For Distribution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let’s walk through each of these steps together!&lt;/p&gt;

&lt;p&gt;Ready? Let’s go! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Workflow Setup
&lt;/h2&gt;

&lt;p&gt;In this section, we’ll add a basic GitHub workflow setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Creating a Workflow File
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to your project’s root directory.&lt;/li&gt;
&lt;li&gt;If you don’t already have one, create a new directory named .github/workflows.&lt;/li&gt;
&lt;li&gt;Inside this directory, create a new YAML file (e.g., ios.yml).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ft3akd7g21kvb7lqqkokx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ft3akd7g21kvb7lqqkokx.png" alt="Image description" width="200" height="163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Set up workflow trigger
&lt;/h3&gt;

&lt;p&gt;In the newly created YAML file, start by defining the workflow trigger&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Publish to App Store Connect

on:
 push:
 branches:
 - main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;name:&lt;/strong&gt; This is the name of the workflow. It will be displayed under the Actions tab in your GitHub repository.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;on:&lt;/strong&gt; This defines the event that triggers the workflow. In this case, the workflow will be triggered automatically whenever code is pushed to the &lt;code&gt;main&lt;/code&gt; branch (e.g. when a sub-branch is merged into &lt;code&gt;main&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;There are other events you can use as triggers depending on your workflow needs, such as pull requests, tags, or schedule-based triggers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Set up the jobs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  ios_deploy_testflight:
    runs-on:  macos-13
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A workflow is made up of one or more jobs.&lt;/p&gt;

&lt;p&gt;Each job runs in a specific environment, specified by the &lt;code&gt;runs-on&lt;/code&gt; attribute. In this case, we're using a macOS environment (&lt;code&gt;macos-13&lt;/code&gt;), which is essential for building iOS apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Add the jobs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;steps:
  - name: Checkout
    uses: actions/checkout@v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checkout: This action checks out your repository into the $GITHUB_WORKSPACE, allowing your workflow to access the code and resources in your repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Set up Flutter SDK
    uses: subosito/flutter-action@v2
    with:
      flutter-version: 3.19.0
      cache: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set up Flutter SDK: This step configures the Flutter environment in GitHub Actions. Make sure the specified version is compatible with the version in your pubspec.yaml file. &lt;/p&gt;

&lt;p&gt;By default, this action will use the latest version, but you can specify a different version as needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Install dependencies and lint check
  run: |
    flutter clean
    flutter pub get
    dart analyze --fatal-infos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install dependencies and lint check: In this step, we run &lt;code&gt;flutter pub get&lt;/code&gt; to install all necessary dependencies for the project. &lt;/p&gt;

&lt;p&gt;We also perform a lint check using &lt;code&gt;dart analyze --fatal-infos&lt;/code&gt;. This lint check is optional; you can skip it if you prefer not to perform static analysis on your code.&lt;/p&gt;

&lt;p&gt;Once you push this workflow file to the main branch, you will see a build queued in the Actions tab of the GitHub repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F837yqfq25e0diey17zdv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F837yqfq25e0diey17zdv.png" alt="Workflow on GitHub Action Tab" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting things up in App Store Connect
&lt;/h2&gt;

&lt;p&gt;Before we proceed, we need to set up your app in App Store Connect. Skip this step if you’ve already done it.&lt;/p&gt;

&lt;p&gt;Let’s walk through them. 👇&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a Distribution certificate
&lt;/h3&gt;

&lt;p&gt;You can follow this guide if you don’t have it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Skip this if you’ve a Distribution certificate already.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Create an App Bundle identifier
&lt;/h3&gt;

&lt;p&gt;Go to Identifiers and create a new ID. This bundle ID is required to create an app in the App Store.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwc2i2jdnwz2n392esujy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwc2i2jdnwz2n392esujy.png" alt="Create an App Bundle Identifier" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select App IDs and continue.&lt;/li&gt;
&lt;li&gt;Select App to Register.&lt;/li&gt;
&lt;li&gt;Provide a Description, Bundle ID, and check capabilities you need in the app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fjx2m03a5f02bx4696dl9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjx2m03a5f02bx4696dl9.png" alt="Add Description, Bundle ID" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click continue and now you have a new identifier for the app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Create a Provisioning Profile
&lt;/h3&gt;

&lt;p&gt;A Provisioning Profile is linked to a Distribution Certificate and identifies a team or organization. This profile authorizes your app to run on devices without needing Xcode.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to the Profiles section and click on the + sign to create a new Provisioning Profile.&lt;/li&gt;
&lt;li&gt;Select App Store Connect under Distibution and click Continue.&lt;/li&gt;
&lt;li&gt;Choose the App ID you created earlier and click Continue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F2q3z5el1tepjxixxaely.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F2q3z5el1tepjxixxaely.png" alt="Select an App ID" width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select the Distribution Certificate for the Provisioning Profile and click Continue.&lt;/li&gt;
&lt;li&gt;Add the Provisioning Profile name and click Continue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fybik38wvlkpi80b2g718.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fybik38wvlkpi80b2g718.png" alt="Download Provisioning Profile" width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download the Provisioning Profile.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Create a New App
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Go to Apps and create a New App&lt;/li&gt;
&lt;li&gt;Fill in the required details in the form and click on Create.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fkmq7ycatqiemtrgcah9z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fkmq7ycatqiemtrgcah9z.png" alt="Create a New App on App Store" width="800" height="926"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Congratulations! You have successfully created a new app.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Generate an API key
&lt;/h3&gt;

&lt;p&gt;As per the Codemagic &lt;a href="https://blog.codemagic.io/deploy-your-app-to-app-store-with-codemagic-cli-tools-and-github-actions/" rel="noopener noreferrer"&gt;guide&lt;/a&gt;, to enable Codemagic CLI tools to upload and fetch data from App Store Connect, you’ll need to generate an App Store Connect API key with App Manager access.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Log in to App Store Connect and navigate to Users and Access &amp;gt; Integrations.&lt;/li&gt;
&lt;li&gt;Click on the + sign to generate a new API key. &lt;/li&gt;
&lt;li&gt;Provide a name for the key and choose an App Manager access level.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ff6u5b3x3jsdrkil6resg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ff6u5b3x3jsdrkil6resg.png" alt="Create a New Api Key" width="245" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After generating the key, it will be listed among the active keys. Click Download API Key to save the private key for later use. &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note: The key can only be downloaded once, so be sure to keep it at a secure location.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Frazp4g19r28fiamogsqc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Frazp4g19r28fiamogsqc.png" alt="Download Api Key" width="750" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Save this Issuer ID, KEY ID, and the API Key, as we’ll need it later. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Download the Certificate
&lt;/h3&gt;

&lt;p&gt;Download the distribution certificate you created in Step 5.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fgxxuyqvnkhk1ce51os60.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fgxxuyqvnkhk1ce51os60.png" alt="Download the Distibution Certificate" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Locate the downloaded certificate and open it using Keychain Access. Next, Export the certificate to create a file with a &lt;code&gt;.p12&lt;/code&gt; extension. You can save this file in your Downloads folder or any directory you prefer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwau6l41prlh0wcoppne9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwau6l41prlh0wcoppne9.png" alt="Download the Certificate From KeyChain" width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When you click to download, you’ll be prompted to set a password for the certificate. This password will be required to access the certificate, so make sure to remember it. You will need to add it as a variable in your workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fuftcmjjilrqkgowx9r9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fuftcmjjilrqkgowx9r9j.png" alt="Set Certificate Password" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we’ve completed all the basic setups for App Store Connect, it’s time to configure the environment variables for GitHub Actions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To read the Full blog on our platform, please visit &lt;strong&gt;&lt;a href="https://canopas.com/automate-flutter-ios-app-deployment-with-github-actions-and-codemagic-cli-4d063ea6ef08" rel="noopener noreferrer"&gt;Canopas blog&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;If you like what you read, be sure to hit 💖 button! — as a writer it means the world!&lt;/p&gt;

&lt;p&gt;I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.&lt;/p&gt;

&lt;p&gt;Happy coding! 👋&lt;/p&gt;

</description>
      <category>ios</category>
      <category>flutter</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to write E2E tests for Next.js Application using Playwright</title>
      <dc:creator>Nandani Sharma</dc:creator>
      <pubDate>Tue, 29 Oct 2024 08:48:24 +0000</pubDate>
      <link>https://dev.to/canopassoftware/how-to-write-e2e-tests-for-nextjs-application-using-playwright-94a</link>
      <guid>https://dev.to/canopassoftware/how-to-write-e2e-tests-for-nextjs-application-using-playwright-94a</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Imagine building a house without carefully reviewing the blueprints or measuring the materials. &lt;/p&gt;

&lt;p&gt;You might have misaligned walls, incorrectly sized windows, or structural issues. Similarly, while developing an application, neglecting tests can lead to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unexpected errors:&lt;/strong&gt; Bugs and errors can go unnoticed until users face your application, potentially causing significant problems to users. Of course, That you will never want!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poor user experience:&lt;/strong&gt; A buggy application can frustrate users and damage your reputation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Increased maintenance costs:&lt;/strong&gt; Fixing bugs after they’ve been discovered can be time-consuming and expensive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By investing time in testing your code, you’re essentially building a solid foundation for your application, ensuring its reliability, quality, and maintainability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why E2E tests?
&lt;/h2&gt;

&lt;p&gt;E2E tests validate that the entire user journey functions correctly, from login to checkout or any other complex process. They ensure that the application provides a seamless and intuitive user experience.&lt;/p&gt;

&lt;p&gt;We can easily detect any broken link, invalid user input, or any UI/UX problem that is laughing in the corner!&lt;/p&gt;

&lt;p&gt;You might say, the tests take up much of my time 😨. Trust me, it will be worth it!!&lt;/p&gt;

&lt;p&gt;Multiple tools are available for testing, such as Jest, Cypress, Playwright, etc. Each has advantages and drawbacks.&lt;/p&gt;

&lt;p&gt;As we are going to perform End-to-End tests, Playwright is the testing tool that has more advantages over any other tools, specifically Cypress.&lt;/p&gt;

&lt;p&gt;Hence, we will go with the &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;playwright&lt;/strong&gt;&lt;/a&gt; in this blog.&lt;/p&gt;

&lt;p&gt;Without further ado, let’s go ahead!!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Consider installing Node.js as a first step, if you don’t have one already.&lt;/p&gt;

&lt;p&gt;Before we start, let’s first create a Next.js application. We will use it throughout the blog.&lt;/p&gt;

&lt;p&gt;Execute the below command to create the Next.js app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx create-next-app@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will ask questions for configuring the app, respond to them, and we’re ready with the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Execute the command to install the playwright.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init playwright@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will ask you certain questions for configuring the playwright tool in your project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;After completing the configuration, you will find some of the files created inside the project folder, but we will only need the &lt;strong&gt;playwright.config.ts&lt;/strong&gt; file and the &lt;strong&gt;tests&lt;/strong&gt; directory from them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;playwright.config.ts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig, devices } from '@playwright/test';

/**
 * Read environment variables from file.
 * https://github.com/motdotla/dotenv
 */
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });

/**
 * See https://playwright.dev/docs/test-configuration.
 */
export default defineConfig({
  testDir: './tests',
  /* Run tests in files in parallel */
  fullyParallel: true,
  /* Fail the build on CI if you accidentally left test.only in the source code. */
  forbidOnly: !!process.env.CI,
  /* Retry on CI only */
  retries: process.env.CI ? 2 : 0,
  /* Opt out of parallel tests on CI. */
  workers: process.env.CI ? 1 : undefined,
  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
  reporter: 'html',
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
  use: {
    /* Base URL to use in actions like `await page.goto('/')`. */
    // baseURL: 'http://127.0.0.1:3000',

    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: 'on-first-retry',
  },

  /* Configure projects for major browsers */
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },

    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },

    // {
    //   name: 'webkit',
    //   use: { ...devices['Desktop Safari'] },
    // },

    /* Test against mobile viewports. */
    // {
    //   name: 'Mobile Chrome',
    //   use: { ...devices['Pixel 5'] },
    // },
    // {
    //   name: 'Mobile Safari',
    //   use: { ...devices['iPhone 12'] },
    // },

    /* Test against branded browsers. */
    // {
    //   name: 'Microsoft Edge',
    //   use: { ...devices['Desktop Edge'], channel: 'msedge' },
    // },
    // {
    //   name: 'Google Chrome',
    //   use: { ...devices['Desktop Chrome'], channel: 'chrome' },
    // },
  ],

  /* Run your local dev server before starting the tests */
  // webServer: {
  //   command: 'npm run start',
  //   url: 'http://127.0.0.1:3000',
  //   reuseExistingServer: !process.env.CI,
  // },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;testDir — &lt;/strong&gt; Path where our test files live&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;projects —&lt;/strong&gt;  Defines the browsers, in which we want to run the tests. It will run all the provided tests in all browsers. &lt;br&gt;
i.e. total test executions will be &lt;strong&gt;no. of test cases * no. of browsers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;webServer — &lt;/strong&gt; This block presents our server url, for lcoal environment it will be &lt;strong&gt;&lt;a href="http://127.0.0.1:3000" rel="noopener noreferrer"&gt;http://127.0.0.1:3000&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;modify the webServer block as below, if the &lt;strong&gt;url&lt;/strong&gt; doesn’t load local server:&lt;br&gt;
 &lt;strong&gt;webServer: {&lt;br&gt;
 command: ‘npm run start’,&lt;br&gt;
 port: 3000,&lt;br&gt;
 reuseExistingServer: true,&lt;br&gt;
}&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Notes:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It also provides an option to mention a specific device or a browser on which we want to execute tests. P.S. &lt;strong&gt;/* Test against mobile viewports. */&lt;/strong&gt; section.&lt;/li&gt;
&lt;li&gt;Before running the tests, ensure the application is already running on the &lt;strong&gt;url&lt;/strong&gt; provided in the &lt;strong&gt;webserver&lt;/strong&gt; config. If it is running on a different port, consider updating it accordingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implement user login flow
&lt;/h2&gt;

&lt;p&gt;To test user login flow, let’s first create it. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please note that I have used an App router in Next.js that’s why I added the login page at &lt;strong&gt;/src/app&lt;/strong&gt;.&lt;br&gt;
If you use a Page router then add the &lt;strong&gt;/login/page.tsx&lt;/strong&gt; into &lt;strong&gt;/src/pages&lt;/strong&gt; directory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;src/app/login/page.tsx&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"use client";

import { useState } from 'react';
import { FormEvent } from 'react'

export default function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errorMessage, setErrorMessage] = useState("");

  const handleLogin = async (e: FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
    e.preventDefault();

    // Validate the form
    if (!email || !password) {
      setErrorMessage('Please fill in all fields');
      return;
    }

    // Simulate a login request
    if (email === "johndoe@gmail.com" &amp;amp;&amp;amp; password === "pass1234") {
      window.location.href = "/";
    }
  };

  return (
    &amp;lt;div className='flex justify-center items-center h-screen'&amp;gt;
      &amp;lt;form onSubmit={handleLogin} className='flex flex-col gap-3 w-[300px] text-black'&amp;gt;
      &amp;lt;input
        type="email"
        placeholder="Email"
        value={email}
        onChange={(e) =&amp;gt; setEmail(e.target.value)}
        className='p-3'
      /&amp;gt;
      &amp;lt;input
        type="password"
        placeholder="Password"
        value={password}
        onChange={(e) =&amp;gt; setPassword(e.target.value)}
        className='p-3'
      /&amp;gt;
      &amp;lt;button type="submit" className='bg-blue-500 p-3 w-fit mx-auto'&amp;gt;Login&amp;lt;/button&amp;gt;
      {errorMessage &amp;amp;&amp;amp; &amp;lt;p&amp;gt;{errorMessage}&amp;lt;/p&amp;gt;}
    &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this simple component, we’re expecting users to input their email and password to log in to the application.&lt;/p&gt;

&lt;p&gt;For simplicity purposes, I have simulated the login request by just matching the email and password to static values(Don’t take it seriously though😃).&lt;/p&gt;

&lt;p&gt;There are two scenarios,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If the login succeeds — Redirect the user to the home screen(“/”)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;In case login fails — Stay on the login page and show the error message&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s test both scenarios now. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P.S. Make sure the app is running while executing the tests, as we will need to test redirection using a real URL.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  End-to-End testing
&lt;/h2&gt;

&lt;p&gt;As we have configured &lt;code&gt;/tests&lt;/code&gt; directory in the &lt;strong&gt;playwright.config.ts&lt;/strong&gt;, we asked the playwright to look into this directory and execute tests from there. Consider modifying it, if you want to use some other directory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F7joubm5n3eo18l8fr467.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F7joubm5n3eo18l8fr467.png" alt="Playwright config for tests directory" width="694" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s create &lt;strong&gt;login.spec.ts&lt;/strong&gt; in the &lt;strong&gt;tests&lt;/strong&gt; directory, you can name it whatever you want (There’s no convention for it).&lt;/p&gt;

&lt;p&gt;For simplicity purposes, I have named it as the feature name.&lt;/p&gt;

&lt;p&gt;Let’s first collect the steps, we are going to test for successful login.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to the login page&lt;/li&gt;
&lt;li&gt;Fill in the email and password fields&lt;/li&gt;
&lt;li&gt;Click on the &lt;strong&gt;Login&lt;/strong&gt; button&lt;/li&gt;
&lt;li&gt;Next steps will be executed according to the login success/failure scenario&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;For a successful login case — We will verify whether the current page is the home page&lt;/li&gt;
&lt;li&gt;For failure case — We will check the current page is login and the error message is set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s write our first test case for &lt;strong&gt;successful&lt;/strong&gt; login.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test('Login form successfully redirects on valid credentials', async ({ page }) =&amp;gt; {
  // Navigate to the login page  
  await page.goto('/login'); // Replace with your development URL

  // Fill in the email and password fields
  await page.fill('input[type="email"]', 'johndoe@gmail.com');
  await page.fill('input[type="password"]', 'pass1234');

  // Click the login button
  await page.click('button[type="submit"]');

  // Wait for the page to redirect
  await page.waitForURL("/");

  // Assert that the user is redirected to the homepage
  await expect(page).toHaveURL('/'); // Replace with your homepage URL
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we have provided the valid credentials and thus the login check passed and redirected to the home page.&lt;/p&gt;

&lt;p&gt;Let’s check by running the test command inside the project directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx playwright test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Hurray! Our first test is passed successfully🎉🎊.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We have written only one test case, but it shows two tests while executing the tests as below. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdul8fsa0h0ae21w48tg2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdul8fsa0h0ae21w48tg2.png" alt="Login success test cases executed" width="800" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is it like that?
&lt;/h2&gt;

&lt;p&gt;It’s because we opted for two simulated browsers using &lt;strong&gt;playwright.config.ts&lt;/strong&gt; file. All the tests will be executed in each browser we have mentioned.&lt;/p&gt;

&lt;p&gt;Let’s write the failure test cases now. We will consider two cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Requesting login with empty email and password&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test('Login form displays error message on empty credentials', async ({ page }) =&amp;gt; {
  await page.goto('/login');

  // Click the login button
  await page.click('button[type="submit"]');

  // Check if the error message is displayed
  const errorMessage = await page.textContent('p');
  await expect(errorMessage).toContain('Please fill in all fields');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Requesting login with wrong credentials&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test('Login form displays error message on invalid credentials', async ({ page }) =&amp;gt; {
  await page.goto('/login');

  // Fill in the email field only
  await page.fill('input[type="email"]', 'test@gmail.com');
  await page.fill('input[type="password"]', 'pass1234');

  // Click the login button
  await page.click('button[type="submit"]');

  const errorMessage = await page.textContent('p');
  await expect(errorMessage).toContain('Invalid credentials');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s rerun the tests to check whether all of our test cases pass or not. And yay! it all passed🎉&lt;/p&gt;

&lt;p&gt;The playwright tool also provides a test report. Run the below command to see the report. It will open a browser page as below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx playwright show-report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2Fso3sgpzv2l1qsqshzvr7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fso3sgpzv2l1qsqshzvr7.png" alt="Test report" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;A playwright is a powerful tool for writing E2E tests.&lt;/p&gt;

&lt;p&gt;E2E tests ensure your application feature works as expected from start to finish.&lt;/p&gt;

&lt;p&gt;For performing E2E tests in Next.js, the essential steps are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setup Playwright &lt;/li&gt;
&lt;li&gt;Create test files &lt;/li&gt;
&lt;li&gt;Write test cases&lt;/li&gt;
&lt;li&gt;Execute the tests&lt;/li&gt;
&lt;li&gt;Show the test report for a visual representation of the test results&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;To read the Full blog on our platform, please visit &lt;strong&gt;&lt;a href="https://canopas.com/how-to-write-E2E-tests-for-nextjs-application-using-playwright" rel="noopener noreferrer"&gt;Canopas blog&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;If you like what you read, be sure to hit 💖 button! — as a writer it means the world!&lt;/p&gt;

&lt;p&gt;I encourage you to share your thoughts in the comments section below. Your input not only enriches our content but also fuels our motivation to create more valuable and informative articles for you.&lt;/p&gt;

&lt;p&gt;Happy coding! 👋&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>playwright</category>
      <category>testing</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
