Skip to content

Commit

Permalink
Update practical part
Browse files Browse the repository at this point in the history
  • Loading branch information
bdmendes committed Mar 5, 2024
1 parent 7f4bc9e commit 7974c80
Show file tree
Hide file tree
Showing 141 changed files with 5,239 additions and 14 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added flutter-workshop/assets/practical/fetch_news.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added flutter-workshop/assets/practical/listview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added flutter-workshop/assets/practical/new_project.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added flutter-workshop/assets/practical/news_card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed flutter-workshop/assets/task_result.png
Binary file not shown.
305 changes: 291 additions & 14 deletions flutter-workshop/presentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class: inverse
- Widgets, Layout, Material Design
- Advanced topics: state management, platform channels, offline storage, architecture
- Best practices
- A hands-on session on _uni_
- A hands-on session starting with a fresh project

---

Expand Down Expand Up @@ -498,35 +498,312 @@ class: center, middle, inverse

### So you are now a Flutter expert

#### Let's get our hands dirty
#### Let's get our hands dirty. Can you build this (or better)?

<div style="display: flex; justify-content: center; align-items: center; gap: 2em">
<img src="./assets/practical/fetch_news.png" style="height: 50vh;">
<img src="./assets/practical/news_details.png" style="height: 50vh;">
</div>

---

### Flutter: Hands-on
### Bootstrapping a new project

- Make sure you have Flutter well configured on your machine:

```sh
flutter doctor
```

- Clone the _uni_ repository:
- Create a new Flutter project and run it on your device:

```sh
git clone [email protected]:NIAEFEUP/uni.git
flutter create news_app && cd news_app && flutter run
```

<div style="display: flex; justify-content: center; align-items: center;">
<img src="./assets/practical/new_project.png" style="height: 50vh;">
</div>

---

### Removing some demo code

- Change the accent color to `Colors.blue`
- Change the title of the app to "News App"
- Remove the counter, the increment button and the center text

<div style="display: flex; justify-content: center; align-items: center;">
<img src="./assets/practical/blank_project.png" style="height: 50vh;">
</div>

---

### Creating a news card widget

- Create a new file `news_card.dart` and define a `NewsCard` stateless widget
- You can hardcode some strings for now
- Wrap the `Card` widget and customize as you see fit
- Add it on the homepage

<div style="display: flex; justify-content: center; align-items: center;">
<img src="./assets/practical/news_card.png" style="height: 50vh;">
</div>

---

### Creating a news card widget

```dart
class NewsCard extends StatelessWidget {
const NewsCard({super.key});
@override
Widget build(BuildContext context) {
return Card(
child: Row(
children: [
Container(
child: Image.network(
"https://image.cnbcfm.com/api/v1/image/105692413-1548173252068gettyimages-450710900.jpeg?v=1548173273",
fit: BoxFit.cover,
)),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Ronaldo is upset",
),
Text(
"He is getting a bit old and it's somewhat hard to drible. Still a legend though."),
],
))
],
));
}
}
```

---

### Making the widget reusable

- Create a new file `news_article.dart` and define a `NewsArticle` class that holds the article's data
- Make the `NewsCard` widget accept a `NewsArticle` object as a parameter and use it to populate the children fields
- Create some articles on the homepage using a ListView

<div style="display: flex; justify-content: center; align-items: center;">
<img src="./assets/practical/listview.png" style="height: 50vh;">
</div>

---

### Making the widget reusable

```dart
class NewsArticle {
String? imageUrl;
String title;
String subtitle;
NewsArticle({this.imageUrl, required this.title, required this.subtitle});
}
```

```dart
Widget build(BuildContext context) {
return Card(
child: Row(
children: [
Container(
child: Image.network(
article.imageUrl ??
"https://image.cnbcfm.com/api/v1/image/105692413-1548173252068gettyimages-450710900.jpeg?v=1548173273",
fit: BoxFit.cover)),
Expanded(
child: Column(
children: [
Text(article.title),
Text(article.subtitle),
],
))
],
));
}
```

---

### Making the widget reusable

```dart
class _MyHomePageState extends State<MyHomePage> {
final List<NewsArticle> articles = [
NewsArticle(
title: "Ronaldo is beautiful",
subtitle: "At least according to Georgina."),
NewsArticle(
title: "FC Porto beats Benfica",
subtitle: "It seemed like it was a Champions League game",
imageUrl:
"https://sportal365images.com/process/smp-images-production/abola.pt/03032024/b61a1758-33bd-4f8a-af5d-a6037c287b89.jpg")
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Container(
padding: const EdgeInsets.all(10),
child: ListView(
children: articles.map((article) => NewsCard(article)).toList(),
)),
);
}
}
```

---

### Fetching some actual data

- Create a new file `articles_fetcher.dart` and add a function to fetch articles from a remote API
- I suggest you use `https://saurav.tech/NewsAPI/top-headlines/category/<category>/in.json`
- Make sure that your function returns `Future<List<NewsArticle>>`
- You need to add the `http` package to your `pubspec.yaml` file
- Use a `FutureBuilder` widget to display the articles on the homepage

<div style="display: flex; justify-content: center; align-items: center;">
<img src="./assets/practical/fetch_news.png" style="height: 50vh;">
</div>

---

### Fetching some actual data

```dart
Future<List<NewsArticle>> fetchArticles(String category) async {
final response = await get(Uri.parse(
"https://saurav.tech/NewsAPI/top-headlines/category/$category/in.json"));
final json = jsonDecode(response.body);
final articles = (json["articles"] as List).map((article) {
return NewsArticle(
title: article["title"],
subtitle: article["description"],
imageUrl: article["urlToImage"]);
}).toList();
return articles;
}
```

- Run the application on your emulator/device
- Follow the steps:
- Create a `weather.dart` file in `lib/view/weather`
- It should contain a widget that fetches data from the [wttr.in](https://github.com/chubin/wttr.in) API upon the press of a button and displays it on the screen. The widget state should extend `GeneralPageViewState`
- Create a new `DrawerItem` in `lib/utils/drawer_items.dart` for the weather screen
- Create the route for the weather screen in `lib/main.dart`
```dart
FutureBuilder(
future: fetchArticles("sports"),
builder: (BuildContext context,
AsyncSnapshot<List<NewsArticle>> snapshot) {
if (snapshot.hasError) {
return Text("Error: ${snapshot.error}");
}
if (snapshot.hasData) {
return ListView(
children: snapshot.data!.map((a) => NewsCard(a)).toList());
}
return const Center(
child: CircularProgressIndicator(),
);
},
)
```

---

### Flutter: Hands-on
### Implementing a details page

- Add a `content` field to the `NewsArticle` model class and update the fetcher accordingly
- Create a new file `news_details.dart` and define a `NewsDetails` page
- Navigate to the details page route when tapping the news card (e.g use a `InkWell` widget)

The final result should look like this:
<div style="display: flex; justify-content: center; align-items: center;">
<img src="./assets/practical/news_details.png" style="height: 50vh;">
</div>

---

### Implementing a details page

```dart
class NewsDetailsPage extends StatelessWidget {
final NewsArticle article;
const NewsDetailsPage(this.article, {super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(article.title),
),
body: ListView(children: [
if (article.imageUrl != null) Image.network(article.imageUrl!),
Text(article.title, style: const TextStyle(fontSize: 20),),
if (article.content != null) Text(article.content!),
]));
}
}
```

```dart
InkWell(
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => NewsDetailsPage(article))),
child: Card(
child: Row(
children: [
Container(
padding: const EdgeInsets.all(10),
width: 150,
height: 150,
child: Image.network(
article.imageUrl ??
"https://image.cnbcfm.com/api/v1/image/105692413-1548173252068gettyimages-450710900.jpeg?v=1548173273",
fit: BoxFit.cover)),
const SizedBox(
width: 5,
),
// We need to "expand this widget" to give it intrinsic width in the row
// Otherwise the text would overflow
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
article.title,
style: const TextStyle(fontSize: 20),
),
Text(article.subtitle),
],
))
],
)))
```

---

class: center, middle, inverse

### Of course, now can you can make it look better

#### But this is a good start

---

class: center, middle, inverse

<img src="./assets/task_result.png" style="width: 32%">
### Thank you for your attention :)
44 changes: 44 additions & 0 deletions flutter-workshop/workshop_demo_project/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
Loading

0 comments on commit 7974c80

Please sign in to comment.