Flutter Solid Principles
S — The Single Responsibility Principle (SRP):
Bad
@override
Widget build(BuildContext context) {
return MaterialApp(
home: new Scaffold(
body: FutureBuilder(
future: getFeedsFromApi(),
builder: (context, snapshot) {
switch(snapshot.connectionState){
case ConnectionState.waiting:
return EmptySpendsPage();
case ConnectionState.none:
return EmptySpendsPage();
case ConnectionState.active:
return EmptySpendsPage();
case ConnectionState.done:
return ListView.builder(
itemCount: snapshot.data == null ? 0 : snapshot.data.length,
itemBuilder: (context, position) {
FeedItem feedItem = snapshot.data[position];
return null;
});
}
}
)
),
);
}
Good
@override
Widget build(BuildContext context) {
bloc.add(NoParams());
return Scaffold(
body: BlocBuilder<FeedsBloc, FeedsState>(
bloc: bloc,
builder: (BuildContext context, FeedsState feedState) {
if (feedState is FeedsLoading) {
return Center(child: CircularProgressIndicator());
} else if (feedState is FeedsError) {
return Text('Ayo! Error');
} else if (feedState is FeedsLoaded) {
return FeedList(feeds: feedState.feedItems);
}
return Container(
color: Colors.orangeAccent,
height: double.infinity,
width: double.infinity);
}),
);
}
O — The Open-Closed Principle (OCP)
Bad
void main() {
MileageCalculator mileageCalculator = new MileageCalculator();
mileageCalculator.showMileage(new Bike());
}
class MileageCalculator {
void showMileage(dynamic anyVehicle) {
if(anyVehicle is Bike) {
print(anyVehicle.getBikeMileage());
} else if(anyVehicle is Car) {
print(anyVehicle.getCarMileage());
}
}
}
class Bike {
String getBikeMileage() => "50";
}
class Car {
String getCarMileage() => "12";
}
Good
void main() {
MileageCalculator mileageCalculator = new MileageCalculator();
mileageCalculator.showMileage(new Car());
}
class MileageCalculator {
void showMileage(Vehicle vehicle) {
print(vehicle.getMileage());
}
}
class Bike extends Vehicle {
String getMileage() => "50";
}
class Car extends Vehicle {
String getMileage() => "12";
}
abstract class Vehicle {
String getMileage();
}
L - The Liskov Substitution Principle (LSP)
Bad
void main() {
StatelessWidget adapter = new StatelessWidget();
adapter.select(new RadioButtonWidget());
}
class StatelessWidget {
void select(ClickListener clickListener) {
if (clickListener is ListItemWidget) {
clickListener.changeTheBackground();
} else if (clickListener is RadioButtonWidget) {
clickListener.check();
}
clickListener.onClick(1);
}
}
mixin ClickListener {
void onClick(int position);
}
class ListItemWidget implements ClickListener {
@override
void onClick(int position){
print("Clicked list item $position");
}
void changeTheBackground() {
print("Change the background color of the item view");
}
}
class RadioButtonWidget implements ClickListener {
@override
void onClick(int position){
print("Clicked radio button $position");
}
void check() {
print("Enable the radio button");
}
}
Good
void main() {
StatelessWidget adapter = new StatelessWidget();
adapter.select(new RadioButtonWidget());
}
class StatelessWidget {
void select(ClickListener clickListener) {
clickListener.onClick(1);
}
}
mixin ClickListener {
void onClick(int position);
}
class ListItemWidget implements ClickListener {
@override
void onClick(int position){
print("Clicked list item $position");
_changeTheBackground();
}
void _changeTheBackground() {
print("Change the background color of the item view");
}
}
class RadioButtonWidget implements ClickListener {
@override
void onClick(int position){
print("Clicked radio button $position");
_check();
}
void _check() {
print("Enable the radio button");
}
}
I — The Interface Segregation Principle (ISP):
Bad
class ImageViewWidget with OnClickListener {
@override
void onImageClick(int position) {
// Yes, I have received a callback, go to the next activity.
print("Clicked position is $position");
}
@override
void onRadioButtonClick(int position) {
// This is no longer needed for this activity, but still I have been implemented for no use...
}
}
mixin OnClickListener {
void onImageClick(int position);
void onRadioButtonClick(int position);
}
Good
class ImageViewWidget with OnImageClickListener {
@override
void onImageClick(int position) {
// Yes, I have received a callback, go to the next activity.
print("Clicked position is $position");
}
}
mixin OnImageClickListener {
void onImageClick(int position);
}
mixin OnRadioButtonClickListener {
void onRadioButtonClick(int position);
}
D - The Dependency Inversion Principle (DIP)
Bad
class RepositoryImpl {
List<FeedItem> getDataFromApi() {
// do your api call
}
List<FeedItem> getDataFromCache() {
// get the data from the database
}
}
Good
class RepositoryImpl {
RemoteDataSource remoteDataSource;
LocalDataSource locaDataSource;
RepositoryImpl(this.remoteDataSource, this.locaDataSource);
}
abstract class RemoteDataSource {
List<FeedItem> getDataFromApi();
}
abstract class LocalDataSource {
List<FeedItem> getDataFromCache();
}
class RemoteDataSourceImpl extends RemoteDataSource{
// Lets assume this class has an access to http client
List<FeedItem> getDataFromApi(){
// do your api call
}
}
class LocalDataSourceImpl extends LocalDataSource {
// Lets assume this class has an access to database instance
List<FeedItem> getDataFromCache(){
// get the data from the database
}
}