Friday, 6 October 2023

Custom Color in PrimarySwatch (Flutter)

 




import 'package:flutter/material.dart';

void main() {
runApp(
const MyApp(),
);
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
debugShowCheckedModeBanner: false,
theme: ThemeData(
fontFamily: "Inter",
primarySwatch: mycolor,
primaryColor: const Color.fromRGBO(229, 116, 29, 1),
appBarTheme: const AppBarTheme(
backgroundColor: Color.fromRGBO(229, 116, 29, 1),
),
),
home: homeView(),
);
}
}

MaterialColor mycolor = const MaterialColor(
0xFFE5741D,
<int, Color>{
50: Color(0xFFE5741D),
100: Color(0xFFE5741D),
200: Color(0xFFE5741D),
300: Color(0xFFE5741D),
400: Color(0xFFE5741D),
500: Color(0xFFE5741D),
600: Color(0xFFE5741D),
700: Color(0xFFE5741D),
800: Color(0xFFE5741D),
900: Color(0xFFE5741D),
},
);

ARC — Automatic Reference Counting in Swift

 Hello everyone! I’m excited to share my knowledge about ARC (Automatic Reference Counting) and how it works in Swift. 

Automatic Reference Counting (ARC) is used in Swift to manage memory allocation and deallocation. ARC operates automatically, so you don’t need to worry about freeing up an instance from memory. However, it’s important to note that ARC may not catch all memory issues, and you can encounter memory leaks in your code due to mistakes that ARC doesn’t handle. We’ll discuss this topic in more detail later.

# How ARC Works

Every time you create a new instance of a class, ARC allocates a chunk of memory to store information about that instance. This memory holds information about the type of the instance, together with the values of any stored properties associated with that instance.

Additionally, when an instance is no longer needed, ARC frees up the memory used by that instance so that the memory can be used for other purposes instead. This ensures that class instances don’t take up space in memory when they’re no longer needed.

But how does ARC allocate and deallocate information from memory? Swift provides two methods for this: when we want to allocate a chunk of memory, we use the ‘init()’ method, and when we want to deallocate that data, we can use the ‘deinit()’ method.

class Person {
init() {
print("The person is allocated memory.")
}

deinit {
print("The person is freed up from memory")
}
}

var person: Person? = Person()
person = nil

// called both methods
// The person is allocated memory.
// The person is freed up from memory

We have one instance that holds a strong reference to the ‘Person’ class. If we don’t assign ‘nil’ to the ‘person’ instance, we can have a memory leak because the ‘deinit()’ method won’t be called.

We can encounter this issue when working with reference types like classes, closures, and so on. Swift provides us with the opportunity to resolve this issue by using the ‘weak’ or ‘unowned’ keywords.

Before passing to talk about “weak” and “unowned” let’s understand what is Reference cycles(Retain cycle).

# Reference cycles(Retain cycle)
It’s important to mention that strong references increase the retain count of an object, while weak and unowned references do not, which is why they’re used to break retain cycles.

We can encounter this issue when two classes refer to each other and create a retain cycle.

Retain Cycle 🔄
class Person {
var macBook: MacBook?

init() {
print("The person is allocated memory.")
}

deinit {
print("The person is freed up from memory")
}
}

class MacBook {

var person: Person?

init() {
print("The macBook is allocated memory.")
}

deinit {
print("The macBook is freed up from memory")
}
}

var person: Person? = Person()
var macBook: MacBook? = MacBook()

person?.macBook = macBook
macBook?.person = person

person = nil
macBook = nil

In the above example, a retain cycle occurs because we don’t call the ‘deinit()’ methods and fail to deallocate data from memory.

# How we can solve this issue?
First, let’s take the more challenging approach. We need to manually remember all relationships between classes and assign ‘nil’ to all properties to prevent memory leaks. You can add the code below and observe that ‘deinitialized(deinit)’ is called.

person?.macBook = nil
macBook?.person = nil

person = nil
macBook = nil

But this approach is too difficult to remember all properties and assign “nil” with chaining.

Let’s discuss when to use ‘weak’ and ‘unowned’ references in Swift.

First, I’d like to refactor the code to use “weak” and prevent memory leaks.

class Person {
var macBook: MacBook?

init() {
print("The person is allocated memory.")
}

deinit {
print("The person is freed up from memory")
}
}

class MacBook {

let macBookType: String
weak var person: Person?

init(typeOfMac: String) {
self.macBookType = typeOfMac
print("The macBook is allocated memory.")
}

deinit {
print("The macBook is freed up from memory")
}
}

var person: Person? = Person()
person?.macBook = MacBook(typeOfMac: "MacBook Pro 13")
print(person!.macBook!.macBookType)
person = nil
print(person?.macBook?.macBookType ?? "nil since macbook freed up from memory")

// output

// 1. The person is allocated memory.
// 2. The macBook is allocated memory.
// 3. MacBook Pro 13
// 4. The person is freed up from memory
// 5. The macBook is freed up from memory
// 6. 'nil' since macbook freed up from memory

We also can change “weak” to “unowned” and see image is the same.

Choosing Between “weak” and “unowned” References:

  • Use “weak” when there's a possibility that the referenced object can become “nil” (e.g., in delegate relationships).
  • Use “unowned” when you're sure that the referenced object will never become “nil” during the lifetime of the reference (e.g., parent-child relationships where the child always outlives the parent).

In summary, “weak” references are safe in situations where the referenced object can be deallocated, while “unowned” references are used when you're certain that the referenced object will always exist. Choosing between them depends on the specific needs of your code and the relationships between objects.

SOLID Principles in Swift

 Hello everyone! I’m excited to share my knowledge about SOLID Principles and how it organized in Swift. 

SOLID

I thought every developer who wants to have flexible, dynamic, and effective applications should follow SOLID principles.

SOLID represents 5 principles of object-oriented programming:

  1. Single-responsibility principle
  2. Open–closed principle
  3. Liskov substitution principle
  4. Interface segregation principle
  5. Dependency inversion principle

First, let’s briefly explore all of the SOLID principles, and then dive into them with practical examples.

  1. The Single-responsibility principle: “There should never be more than one reason for a class to change.”In other words, every class should have only one responsibility”.
  2. The Open–closed principle: “Software entities … should be open for extension, but closed for modification.”
  3. The Liskov substitution principle: “Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.”
  4. The Interface segregation principle: “Clients should not be forced to depend upon interfaces that they do not use.”
  5. The Dependency inversion principle: “Depend upon abstractions, [not] concretions.”

# Single-responsibility principle
A common mistake in development is creating classes that handle multiple responsibilities. Each class should have a single, well-defined purpose. For instance, avoid creating a class responsible for network calls, handling functions, passing data to ViewControllers, and more.

# Bad Example

class NetworkManager {
static let shared = NetworkManager()

private init() {}

func handleAllActions() {
let userData = getUsers()
let userArray = parseDataToJson(data: userData)
saveDataToDB(users: userArray)
}

func getUsers() -> Data {
// send API request and wait for response
}

func parseDataToJson(data: Data) -> [String] {
// parse the data and convert to array
}

func saveDataToDB(users: [String]) {
// save that array into CoreData...
}
}

# Good Example

class NetworkManager {
var userAPIHandler: UserAPIHandler?
var parseDataHandler: ParseDataHandler?
var saveDataToDBHandler: SaveDataToDBHandler?

init(userAPIHandler: UserAPIHandler, parseDataHandler: ParseDataHandler, saveDataToDBHandler: SaveDataToDBHandler) {
self.userAPIHandler = userAPIHandler
self.parseDataHandler = parseDataHandler
self.saveDataToDBHandler = saveDataToDBHandler
}

func handleAllActions() {
guard let userAPIHandler else { return }
guard let parseDataHandler else { return }
guard let saveDataToDBHandler else { return }

let userData = userAPIHandler.getUsers()
let userArray = parseDataHandler.parseDataToJson(data: userData)
saveDataToDBHandler.saveDataToDB(users: userArray)
}
}

class UserAPIHandler {
func getUsers() -> Data {
//Send API request and wait for a response
}
}

class ParseDataHandler {
func parseDataToJson(data: Data) -> [String] {
// parse the data and convert it to array
}
}

class SaveDataToDBHandler {
func saveDataToDB(users: [String]) {
// save that array into CoreData...
}
}

Notice how each class has a single, clear responsibility and doesn’t perform multiple tasks.

# Open–closed principle
Every class, structure, and so on should open for extension but close for modification. Swift has powerful features that align with this principle, one of which is the Extension feature. For instance, if we want to add functionality to the native String structure that Swift provides but don’t have access to modify the String’s code directly, Swift offers us Extensions to add that functionality. When working with custom types, we have the ability to modify the code. However, it’s essential to always keep the Open/Closed principle in mind, avoid common mistakes, and take care when handling our custom types. Also, the Protocols keep the Open/Close principle.

# Bad Example

class Cat {
var name: String

init(name: String) {
self.name = name
}

func animalInfo() -> String {
return "I am Cat and name is \(self.name)"
}
}

class Fish {
var name: String

init(name: String) {
self.name = name
}

func animalInfo() -> String {
return "I am fish and name is \(self.name)"
}
}

class AnimalsInfo {
func printData() {
let cats = [Cat(name: "Luna"), Cat(name: "Tina"), Cat(name: "Moon")]

for cat in cats {
print(cat.animalInfo())
}

let fishes = [Fish(name: "Ishxan"), Fish(name: "Karas"), Fish(name: "Sterlec"), Fish(name: "fish")]
for fish in fishes {
print(fish.animalInfo())
}
}
}

let infoOfAnimals = AnimalsInfo()
infoOfAnimals.printData()

This is a flawed example because adding a new type of animal in the future would require changing the ‘AnimalsInfo’ class’s ‘printData’ function, violating the Open/Closed Principle.

# Good Example

protocol Info {
func animalInfo() -> String
}

class Cat: Info {
var name: String

init(name: String) {
self.name = name
}

func animalInfo() -> String {
return "I am Cat and name is \(self.name)"
}
}

class Fish: Info {
var name: String

init(name: String) {
self.name = name
}

func animalInfo() -> String {
return "I am fish and name is \(self.name)"
}
}

class AnimalsInfo {
func printData() {
let animalsInfo: [Info] = [
Cat(name: "Luna"),
Cat(name: "Tina"),
Cat(name: "Moon"),
Fish(name: "Ishxan"),
Fish(name: "Karas"),
Fish(name: "Sterlec"),
Fish(name: "fish")
]

for info in animalsInfo {
print(info.animalInfo())
}
}
}

let infoOfAnimals = AnimalsInfo()
infoOfAnimals.printData()

As you can see, this approach is highly flexible and effective because I won’t encounter any issues when new animals are added to my file. This is because all classes should conform to the ‘Info’ protocol, which allows us to add extensions without modifying class behavior.

# Another Example with Extension

extension Int {
func multipleInt(of number: Int) -> Int {
return self * number
}

func isNegative() -> Bool {
if self < 0 {
return true
}
return false
}
}

let intValue = 99

print(intValue.multipleInt(of: 10)) // 990
print(intValue.isNegative()) // false

# Liskov substitution principle
Liskov Substitution Principle (LTSY) states that when we inherit from a base class, the subclass should not modify the behavior of the base class functions. Users can utilize both the base class functionality and the child class, which inherits the base class behavior.

# Bad Example

class Operators {
func add(num1: Int, num2: Int) -> Int{
return num1 + num2
}

func sub(num1: Int, num2: Int) -> Int{
return num1 - num2
}
}

class Calculator: Operators {
override func add(num1: Int, num2: Int) -> Int {
return num1 * num2
}

override func sub(num1: Int, num2: Int) -> Int {
return num1 + num2
}
}

let add = Operators()
print(add.add(num1: 5, num2: 5)) // cool works -> 10

let calc = Calculator()
print(calc.add(num1: 5, num2: 5)) // not working... why? The user is angry. -> 25

As you can see, I inherited from ‘Operators’ and modified the behavior of the ‘Operators’ class functions, which is not recommended.

# Good Example

class Operators {
func add(num1: Int, num2: Int) -> Int{
return num1 + num2
}

func sub(num1: Int, num2: Int) -> Int{
return num1 - num2
}
}

class Calculator: Operators {
override func add(num1: Int, num2: Int) -> Int {
return num1 + num2
}

override func sub(num1: Int, num2: Int) -> Int {
return num1 - num2
}

func add(num1: Int, num2: Int, num3: Int) -> Int{
return num1 + num2 + num3
}
}

let add = Operators()
print(add.add(num1: 5, num2: 5)) // cool works -> 10

let calc = Calculator()
print(calc.add(num1: 5, num2: 5)) // cool works -> 10

// also added a new function

print(calc.add(num1: 2, num2: 5, num3: 6))

This is correct because I haven’t altered the behavior of the ‘Base (Parent)’ class, allowing users to use both the base and child classes in the same manner.

# Interface segregation principle
The Interface Segregation Principle states that users should not depend on interfaces or functionality they don’t need. It advises against providing overly complex or hard-to-understand interfaces. The interface should be easy and should not contain complex elements that the user cannot understand or use.

# Bad Example

protocol Animals {
func eat()
func fly()
func swim()
}

class Flamingo: Animals {
func eat() {
print("I can eat")
}

func fly() {
print("I can fly")
}

func swim() {
print("I can swim")
}
}

class Dogs: Animals {
func eat() {
print("I can eat")
}

func fly() {
print("I cannot fly")
fatalError()
}

func swim() {
print("I cannot swim")
fatalError()
}
}

As you can see, the ‘Flamingo’ and ‘Dogs’ classes conform to the ‘Animals’ protocol. However, the ‘Dogs’ class, which cannot fly and swim, has been overloaded with unnecessary functionality. This approach is not recommended. I’ve provided a simple example, but you can apply the same evaluation to your UIKit classes to determine if you are overloading them.

# Good Example

protocol Flyable {
func fly()
}

protocol Swimmable {
func swim()
}

protocol Feedable {
func eat()
}

class Flamingo: Flyable, Swimmable, Feedable {
func eat() {
print("I can eat")
}

func fly() {
print("I can fly")
}

func swim() {
print("I can swim")
}
}

class Dogs: Feedable {
func eat() {
print("I can eat")
}
}

I’ve created three separate protocols to eliminate unnecessary overloading.
The Flamingo can fly, swim, and eat for that reason the “Flamingo” class conforms to three protocols those are “Flyable”, “Swimmable”, and “Feedable” but the “Dogs” cannot fly and swim for that reason the “Dogs” class to conform only “Feedable” protocol.

# Dependency inversion principle
The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules. Instead, they should both depend on abstractions. In other words, you should depend on interfaces, not implementations.

# Bad Example

// Low-level class: Dog
class Dog {
func bark() {
print("Woof!")
}
}

// Low-level class: Cat
class Cat {
func meow() {
print("Meow!")
}
}

// High-level class: AnimalSoundMaker
class AnimalSoundMaker {
let dog: Dog
let cat: Cat

init(dog: Dog, cat: Cat) {
self.dog = dog
self.cat = cat
}

func makeDogSound() {
dog.bark()
}

func makeCatSound() {
cat.meow()
}
}

As you see the “AnimalSoundMaker” is a high-level class that directly depends on the “Dog” and “Cat” classes, which are low-level classes representing specific animals. “AnimalSoundMaker” has separate methods for making dog and cat sounds (makeDogSound() and makeCatSound()), and it directly invokes methods on “Dog” and “Cat” instances. As a result, any change or addition to animal types (adding a new animal like a bird) would require modifications to the “AnimalSoundMaker” class, making it less flexible and harder to maintain.

# Good Example

// Abstraction: Animal
protocol Animal {
func makeSound()
}

// Low-level class: Dog
class Dog: Animal {
func makeSound() {
print("Woof!")
}
}

// Low-level class: Cat
class Cat: Animal {
func makeSound() {
print("Meow!")
}
}

// High-level class: AnimalSoundMaker
class AnimalSoundMaker {
let animal: Animal

init(animal: Animal) {
self.animal = animal
}

func performSound() {
animal.makeSound()
}
}

As you see the “Animal” is an abstraction (protocol) that defines a method “makeSound()”. Both “Dog” and “Cat” classes implement this protocol.

AnimalSoundMaker” is a high-level class that depends on “Animal” abstraction. It accepts an “Animal” instance in its constructor, which can be any class conforming to the “Animal” protocol.

Dog” and “Cat” are low-level classes that implement the “Animal” protocol with their own “makeSound()” implementations. This allows you to add new animal types without modifying “AnimalSoundMaker”, promoting flexibility and maintainability.