Skip to content

Commit

Permalink
add logging and update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
iNoles committed Oct 22, 2024
1 parent 92d8353 commit a07c4cb
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 54 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
/_ReSharper.Caches/
.DS_Store
38 changes: 28 additions & 10 deletions NewsService.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,44 @@
using Firebase.Database;
using Firebase.Database.Query;
using Microsoft.Extensions.Logging;

namespace HackerNews;

public class NewsService
public class NewsService(ILogger<NewsService> logger)
{
private const string FirebaseDatabaseUrl = "https://hacker-news.firebaseio.com/v0/";
private readonly FirebaseClient _firebaseClient = new(FirebaseDatabaseUrl);
private readonly ILogger<NewsService> _logger = logger;

public async Task<string> GetTopStoryAsJson()
{
return await _firebaseClient
.Child("topstories.json?print=pretty")
.OnceAsJsonAsync();
try
{
return await _firebaseClient
.Child("topstories.json?print=pretty")
.OnceAsJsonAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching top stories from Firebase");
throw;
}
}

public async Task<StoryModel> GetTopStory(string topStoryId)
{
return await _firebaseClient
.Child("item")
.Child(topStoryId)
.Child(".json?print=pretty")
.OnceSingleAsync<StoryModel>();
try
{
return await _firebaseClient
.Child("item")
.Child(topStoryId)
.Child(".json?print=pretty")
.OnceSingleAsync<StoryModel>();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error fetching top story with ID: {TopStoryId}", topStoryId);
throw;
}
}
}
}
62 changes: 45 additions & 17 deletions NewsViewModel.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace HackerNews;

public class NewsViewModel: ObservableObject
public class NewsViewModel(NewsService newsService, ILogger<NewsViewModel> logger) : ObservableObject
{
public ObservableCollection<StoryModel> TopStoryCollection { get; } = [];

private readonly NewsService _newsService = new();
public ObservableCollection<StoryModel> TopStoryCollection { get; } = new ObservableCollection<StoryModel>();

private readonly NewsService _newsService = newsService;
private readonly ILogger<NewsViewModel> _logger = logger;

public async Task Refresh()
{
TopStoryCollection.Clear();

await foreach (var story in GetTopStories().ConfigureAwait(false))
// Ensure this method is called on the UI thread
await MainThread.InvokeOnMainThreadAsync(async () =>
{
InsertIntoSortedCollection(TopStoryCollection, (a, b) => b.Score.CompareTo(a.Score), story);
//Console.WriteLine(story.Title);
}
try
{
TopStoryCollection.Clear();
await foreach (var story in GetTopStories().ConfigureAwait(false))
{
InsertIntoSortedCollection(TopStoryCollection, (a, b) => b.Score.CompareTo(a.Score), story);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while refreshing top stories.");
}
});
}

static void InsertIntoSortedCollection<T>(ObservableCollection<T> collection, Comparison<T> comparison, T modelToInsert)
{
if (collection.Count is 0)
Expand All @@ -47,13 +59,29 @@ static void InsertIntoSortedCollection<T>(ObservableCollection<T> collection, Co

async IAsyncEnumerable<StoryModel> GetTopStories()
{
var topStories = await _newsService.GetTopStoryAsJson();
var topStoryJson = JsonConvert.DeserializeObject<List<string>>(topStories);
var getTopStoryTaskList = topStoryJson.Select(x => x.ToString()).ToList();
foreach (var topStoryId in getTopStoryTaskList)
var stories = new List<StoryModel>();

try
{
var topStories = await _newsService.GetTopStoryAsJson();
var topStoryJson = JsonConvert.DeserializeObject<List<string>>(topStories);

foreach (var topStoryId in topStoryJson)
{
var story = await _newsService.GetTopStory(topStoryId);
stories.Add(story); // Add to the temporary list
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while fetching top stories.");
throw;
}

// Yield the results after the try-catch block
foreach (var story in stories)
{
var story = await _newsService.GetTopStory(topStoryId);
yield return story;
}
}
}
}
81 changes: 55 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,60 @@
# HackerNews

A .NET MAUI app for displaying the top posts on Hacker News.

This app demonstrates how to use IAsyncEnumerable + C# 8.0 to improve performance. Thanks to IAsyncEnumerable, the items are added to the list as soon as they're available making the app feel faster and more responsive.

This app also uses the Firebase Realtime Database REST API.
# Hacker News App

Welcome to the Hacker News App! This application fetches and displays the top stories from the Hacker News API, allowing users to stay updated with the latest tech news.

## Features

- Fetches the latest top stories from Hacker News (up to 500 stories).
- Displays story titles, authors, and scores.
- Sorts stories by score in descending order.
- Easy to use and visually appealing interface.

## Technologies Used

- [.NET MAUI](https://dotnet.microsoft.com/apps/maui) for cross-platform app development.
- [Firebase](https://firebase.google.com/) for real-time data storage and retrieval.
- [CommunityToolkit.Mvvm](https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/overview) for MVVM architecture.
- [Newtonsoft.Json](https://www.newtonsoft.com/json) for JSON serialization and deserialization.
- [Microsoft.Extensions.Logging](https://learn.microsoft.com/en-us/dotnet/core/extensions/logging) for logging errors and important information.
- **IAsyncEnumerable** for improved performance when fetching top stories from the Hacker News API.

## Installation

To get started with the Hacker News App, follow these steps:

1. Clone this repository:
```bash
git clone https://github.com/iNoles/HackerNews.git
cd HackerNews
```
2. Open the project in your preferred IDE (e.g., Visual Studio).
3. Restore the NuGet packages:
```bash
dotnet restore
```
4. Build the project:
```bash
dotnet build
```
5. Run the application:
```bash
dotnet run
```

## Usage

1. Launch the application.
2. The app will automatically fetch the top stories from Hacker News.
3. View the stories in a sorted list, with the ability to refresh the list to get the latest stories.

## Screenshots
![Hacker News App Desktop](https://github.com/iNoles/HackerNews/assets/49764/9f4ebdcb-014b-4979-a244-f81c6903f89b)

<img width="1136" alt="desktop" src="https://github.com/iNoles/HackerNews/assets/49764/9f4ebdcb-014b-4979-a244-f81c6903f89b">

## Getting Started

### Prerequisites
- Install the latest version for .NET 8
- Install [.NET MAUI workload](https://docs.microsoft.com/en-us/dotnet/maui/get-started/installation) on your development machine.

### Clone the Repository

```bash
git clone https://github.com/iNoles/HackerNews.git
cd HackerNews
```
## Contributing

### Run and Build
Contributions are what make the open-source community such an amazing place to be, learn, inspire, and create. Any contributions you make are greatly appreciated.

```bash
dotnet build
dotnet run
```
- Fork the project.
- Create your feature branch (e.g., ``git checkout -b feature/AmazingFeature``).
- Commit your changes (e.g., ``git commit -m 'Add some AmazingFeature'``).
- Push to the branch (e.g., ``git push origin feature/AmazingFeature``).

0 comments on commit a07c4cb

Please sign in to comment.