DEV Community

Cover image for MVVM + Observables
Rafael Adolfo
Rafael Adolfo

Posted on

MVVM + Observables

The previous post was a simple list displaying an array of strings.

Now, let's build up this project a little bit, shall we?

Alt Text

Download the project

Download the project with the finished feature, and I'll explain what changed and how does it work now.

We had a pretty much basic folder structure in the first part, the initial project default layout:
Alt Text

Now, there's a place for everything. What I did was to create a folder for each kind of class responsibility.
Alt Text

As we are using the MVVM pattern, we need at least:

  • A Model
  • A View
  • A View Model

Using MVVM basically defines that:

  • We have a model to store our model properties
  • We have a view to display our model and receive user interaction
  • We have a viewmodel that will handle user interaction coming from the view, and send model data to the view

search dev.to for more mvvm info

The View

The project is still displaying a list of products, but now data is coming from the viewmodel.

struct ProductListView: View {
    //MARK: - view model
    @ObservedObject var viewModel = ProductListViewModel()

    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.productList) { product in
                    Text("Description: " + product.description)
                }
            }
            .navigationBarItems(trailing: Button(action: {
                self.viewModel.addNewProduct()
            }, label: {
                Text("Add new product")
            } ))
        }

    }
}
Enter fullscreen mode Exit fullscreen mode

The first version we simply put our array of strings inside the view. But now, our view is referencing a variable inside the viewmodel class.

Notice that the viewmodel is labeled as a @ObservedObject. This means that the view is observing the class, at any change of published variables these changes will reflect in our view.

The Model

struct Product : Identifiable {
    //MARK: - Properties

    var id = UUID()
    var description: String
}

extension Product {

    //MARK: - Default initialization
    init() {
        self.description = "New product"
    }
}
Enter fullscreen mode Exit fullscreen mode

The model is pretty straightforward:

  • There's the description property where we can save the product description value
  • and a default initialisation, so that we don't have to write property values every time we want to create a new product.

The ViewModel

protocol ProductListViewModelProtocol {
    //MARK: - Protocol definition

    var productList: [Product] {get}
    func addNewProduct()
}

final class ProductListViewModel : ProductListViewModelProtocol, ObservableObject {
    //MARK: - Published properties

    @Published var productList: [Product] = []

    init() {
        productList.append(Product(description: "iPhone"))
        productList.append(Product(description: "Macbook Pro"))
    }
}

extension ProductListViewModel {
    //MARK: - Functions implementations

    func addNewProduct() {
        productList.append(Product())
    }
}
Enter fullscreen mode Exit fullscreen mode

Here's where happens all the data and actions flow.

  • We have a protocol to define the class capabilities
  • A observable class, to enable this class be observed
  • I also added a function addNewProduct() so that we can add a new product with the app running, and see the changes reflect in our view thanks to the observable feature.

And that's it.
Hit the play button, and now you'll see the same screen working, with an additional button on the right upper corner.
Click the button to add new products and enjoy.

Alt Text

Top comments (0)