9

💥 The Story of My First A-ha Moment With Jetpack Compose

 2 years ago
source link: https://proandroiddev.com/the-story-of-my-first-a-ha-moment-with-jetpack-compose-c739bceb6b0b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Creating the Core Logic

To create the Sudoku app, I needed a composable that is responsible for drawing the boxes that contain numbers.

I created the building block for the SudokuCell as the following:

This gives us the following preview:

1*sQ4BsXthQeZemgjGYW7PLw.png?q=20
the-story-of-my-first-a-ha-moment-with-jetpack-compose-c739bceb6b0b

It’s worth mentioning that the selection state could have been inside the SudokuCell without having the isSelected and onSelection, but that makes the composable stateful. I used state hoisting to move this logic outside of the composable so it becomes stateless and its state is handled by the higher-ups.

Cell Attributes

If you’ve watched professional Sudoku solvers, you know they use pencil-marks (and other notations) on the grid to mark some cells: If a group of cells in a 3x3 box definitely contain a certain digit (and therefore other cells in that box don’t have that digit), the digit is pencil-marked in the corner of those cells. If a cell should contain one of several specific digits (and not the other digits), the options are pencil-marked in the center of the cell. Or colors are used to highlight a group of cells that are somehow related.

1*aTK5guU4hzpRwKa4LfEtuQ.png?q=20
the-story-of-my-first-a-ha-moment-with-jetpack-compose-c739bceb6b0b
Image taken from the app from Cracking the Cryptic YouTube channel

For example, the two red cells in the image above must be of the same value, the number 2 must be in one of the cells of the first column of the middle box, and the cell at row 7 column 5 cannot have digits other than 1, 4, 5 and 6.

This can be achieved by adding more parameters to SudokuCell function:

It can be seen that the composable function is becoming cluttered and long. The Extract Method refactoring can be used to move the parts for corner and center numbers and coloring to separate functions (you can do this easily by selecting the desired code and pressing Ctrl + Alt + M / Command + Option + M in macOS).

Much better. Remember that those function can also be in separate files.

More Features

What happens when we need to add more functionality and features to the cells? Advanced Sudoku puzzles contains many more shapes. For example, Arrow Sudoku contains arrows inside the boxes, Killer Sudoku contains special borders and there are other variants that add rectangles and circles to each cell to indicate whether the number in that cell is even or odd.

One solution is to add more parameters to the function. But this solution has 3 issues:

1. Our function does not have a single responsibility of rendering a box and having a number inside it. It becomes responsible for anything that is drawn inside that box. That responsibility might be huge!

2. Our function is not closed for modification, meaning any change feature that needs to be added to a cell (like coloring it) has to modify this function.

3. We need to have access to all the new features inside SuduokuCell . For example, SudokuCornerBox cannot be in a different module which we do not have access to. This limits our modularization.

At this point I realized that the current design has to be refactored to be more extensible. I thought about having some kind of a decorator pattern. Having my mind tied to the old view system, I thought about passing some kind of a view-thing to the decorators and let them somehow paint over each other. This works great since all issues that were mentioned can be fixed this way.

I then realized that I don’t even need to pass anything to those decorators!

A-ha!

Any function can be a composable by adding @Composable annotation! That includes abstract functions in interfaces that need to be implemented as a composable function. This gives incredible power to the system as every “cell modifier” can implement an interface, and do its drawing.

Let’s have a look:

SudokuCellData with a set of Attributes

As you can see, the SudokuCell class contains a set of Attributes, all of which define a function called Draw. This function is responsible for drawing this Attribute ‘s logic which can be completely independent of the SudokuCellData. These elements are drawn by a forEach inside the SudokuCell function:

Using the attributes inside SudokuCellData (by a forEach)

In order to add more functionality to each cell, we can implement the Attribute interface, do our drawing in there and it will be drawn in each cell containing this attribute.

Let’s add a few attributes:

New attributes created by implementing the Attribute interface

This solves all of our issues with the multiple-parameters solution:

  1. The SudokuCell function is small and is only responsible for drawing its cell and its number. (Actually, the number itself can now be an Attribute ).
  2. We can add more functionality to the system by extending the Attribute interface, without ever touching the SudokuCell‘s code.
  3. Elements can be defined in different modules without SudokuCell having to be dependent to them.

It’s worth noting that since Jetpack Compose brings the observability by default (through composition), we have another advantage that if a cell and its attributes get updated (by something like user input or selection), the cell’s UI will get automatically updated.

1*d_x6-iF7Xe12iJkhhcAzsA.gif?q=20
the-story-of-my-first-a-ha-moment-with-jetpack-compose-c739bceb6b0b
Final result

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK