We're starting to have a better architecture for these UI apps, but we still have a problem. We shouldn't be repeating code. Since the weight and height slider are so similar we have repeated code here in the view and we have repeated code in the intent. We need to be able to create a generic labeledSlider, and then just reuse that for the weight and the height in order to create this app.
So we're going to do something a bit different now. Let's create a main function that implements a generic labeledSlider, so it can't have anything specific to weight, for instance. Gladly we can start by just deleting code. We're going to keep code only related a single labeledSlider.
We can start in the view, we can remove some code there. Let's add also some class names, like labeledSlider from the container.
The label element, we can give just a simple label class name. Instead of height let's put here slider. Instead of weight we're going to have some kind of value, numeric value. Instead of kilos we want some kind of a unit. Instead of the weight label we need to figure out what that label should be. Also these default values should be different later and such.
The view starts taking a good shape to be generic. Let's check the model. We won't be needing the BMI, the weight and the height, we just need a value.
Later on we need to figure what the units should be and that kind of stuff, so we can delete some code here. I just need to map the weight to the state object that has value and not weight specifically, but let's call it value to make it generic and not change weight but change value. We don't need change height.
The model is starting to take a good shape. We don't need these two streams in the intent, we just need one. Instead of change weight, it's change value. We get that from the slider element.
I think that's enough for creating a generic labeledSlider. If we move this, it changes the label and that's good.
Now as you notice we need to give some unit, and we don't know really which values we should start with. For weight we want these units to be kilos, for height slider we want this unit to be centimeters.
How can we avoid hardcoding these units here inside? We need to be able to provide them from outside.
How can our main function receive these properties, such as label, unit, default values and such? Well, everything comes to the main through these sources. If we want to provide something it needs to come through the sources.
What creates sources are drivers. I'm going to create a fake driver. It's fake because it doesn't cause effects in the external world and also because it's temporary, later we're going to remove it. We're going to call it the props driver.
Drivers are always functions that take a sink stream and return some kind of source. I'm not going to have actually a sink because we don't need to give anything to this driver, we just want to provide an input for our function, main.
Our source is a stream that is just an object that will specify those properties for our app, such as label should be, let's say, weight, unit should be kilos, minimum value should be, let's say, 40, maximum should be 150, and the initial value could the lowest one.
Now those values are available here under sources.props. That gives us the stream that we created down there, so this stream is the props stream.
How do we want to use that? We want to pass that to the model so we can initialize our state with those values. Here the model will get props stream as an argument. Now we just need to use prop stream to define our initial state.
Notice previously we had our initial state defined with a value here inside the startWith, but startWith doesn't take stream as an argument, it doesn't work like that, so we're going to use a different technique where I'm going to map the prop stream.
Once you map you get access to the object of this props and that one has props.init. That's an actual value and that's how we could use it with a startWith.
We do that. This gives us the value changing overtime and that value is initialized with that. Once we return a stream inside the map, then we have a stream of streams and that's how we can use flatten.
One thing is missing. This would only represent the value but we also need the unit, the label, and all of those other ones, so we're just going to map that dynamic value to an object that contains that value, kind of like we had before here. I'm also going to add the label from props.label, the unit from props.unit, the maximum from props.max, the minimum from props.min, and just format this, it looks nicer.
That's what we're going to return from the model. Now our state object has value but it also has a label -- that's what we can use here -- and a unit. It also has a min and a max and we're already using there the state.value. That should be enough.
Now we have weight 40 kilograms and it changes over time here. Notice that these were passed through here, the props stream.
That's how this program is now generic. I could change weight to height, and as you can see, it would change accordingly.
This is how we can start building our generic labeledSlider. Then we're going to see later how we can reuse this labeledSlider in a bigger application.