groue / GRDB.swift

A toolkit for SQLite databases, with a focus on application development
MIT License
6.62k stars 679 forks source link

List in detail view #661

Closed danieleprice123 closed 4 years ago

danieleprice123 commented 4 years ago

Request for help in philosophy and using GRDB in master-detail style app. New user learning Swift, SwiftUI, and GRDB all at the same time.

Trying to update an iOS app that was in Objective C and used a SQL DB manager based on a tutorial on appcoda. It is an automated checklist, broken down into Test Point Groupings, made up of Test Points. A "daily execution checklist" was generated from other tables in the SQL database.
The user would input test point results that were inserted into the daily execution checklist and return pass/fail based on criteria.

In the old app, I would run a new SQL command to select/insert on viewDidLoad() or a button push.

Now with SwiftUI and GRDB, trying to recode, and spinning my wheels with just the basics. Very familiar with SQL, but struggling with Swift and GRDB. Very basic dB to learn and create a simple list of TestGroups, each with a navlink that will show the testPoints in the detail view.

testPoint table id name grouping

testGroup table id name

  1. With the philosophy of right data at right time, what is the best procedure to follow...use a. GRDBCombine with ValueObservation tracking a single checklist and manipulating the array for the SwiftUI List views or b. A separate fetch for each view (fetch TestGroups for master list and separate fetch for list of TestPoints in each TestGroup) and using a TestPoint for observation? Or have I missed the boat entirely?

  2. The TestPointGroup hasMany TestPoints, so using that association as described in the documentation and best practices. I can get the list of TestPointGroups, but I can't generate the list of relevant TestPoints in the detail view. I created another struct: TestPointsGrouped var testGroup: TestGroup var testPoints: [TestPoint]

and want to do another fetch, but how do I provide the testPointGroupId for the group I'm interested in? In other words, based on the examples in your documentation, how would one build a view listing the authors, each with a navLink to a detail view that showed all of those author's books.

Thank you again for your valuable time and any resources you can recommend.

groue commented 4 years ago

Hello @danieleprice123

New user learning Swift, SwiftUI, and GRDB all at the same time

That's a lot 😅 But you do sound like you learned quite a lot already!

Look, I'm not quite able to answer your questions, because I'm very far from fluent with SwiftUI.

But I think you'll find a solution with a little reframing. Your questions do not sound like they are specific to GRDB or GRDBCombine in any way. You would have the same questions if you would replace the database with a remote server.

How do you pass a value from one view to another, and create a new publisher based on this value for the second view? That sounds like the only real question to me.

danieleprice123 commented 4 years ago

Thanks for the reframing...stackoverflow here I come!

On Wed, Dec 4, 2019 at 3:01 PM Gwendal Roué notifications@github.com wrote:

Hello @danieleprice123 https://github.com/danieleprice123

New user learning Swift, SwiftUI, and GRDB all at the same time

That's a lot 😅 But you do sound like you learned quite a lot already!

Look, I'm not quite able to answer your questions, because I'm very far from fluent with SwiftUI.

But I think you'll find a solution with a little reframing. Your questions do not sound like they are specific to GRDB or GRDBCombine in any way. You would have the same questions if you would replace the database with a remote server.

How do you pass a value from one view to another, and create a new publisher based on this value for the second view? That sounds like the only real question to me.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/groue/GRDB.swift/issues/661?email_source=notifications&email_token=AGFH5NT7NKHZOSDD7SVD2ZLQXAEDHA5CNFSM4JVNECVKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEF6KASI#issuecomment-561815625, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGFH5NXQ23WRJCUHOL46NETQXAEDHANCNFSM4JVNECVA .

groue commented 4 years ago

Yes, this is your best option. I can't provide SwiftUI support yet ;-)

groue commented 4 years ago

Yet I'm interested in your solution, as well as future GRDB users who will get stuck on the same question!

danieleprice123 commented 4 years ago

SOLVED! Solution below. I tried to change to match your author/book example, so may have made an error or bad parameter names that confuse the issue. But the concepts are correct and the key is the formatting of the parameter holding the data from the fetch. Please let me know. Derived from the your docs and best practices, combined with tutorials of Audrey Tam and Jessy Catterwaul with follow-on conversations in their comments sections at raywenderlich.com. It's all about index control!

Desired UI: A master list of all authors. Clicking on an author would bring up a detail view that would present all of that author's books.

I'll work backwards to provide the context for the code. In the detail view, I needed a list of books. the unique id would be book.id from the authorInfo:

struct BookRow: View {
  var authorInfo: AuthorInfo 

  var body: some View {
      List(authorInfo.books, id: \.id) {book in 
         Text("\(book.title)")
      }
   }
}

In the master view, the unique index would be the author.id from the authorInfo. Here's the view for each author row. By calling the book row from here and using the same authorInfo parameter, we will limit the detail view to just that author's books!

struct AuthorRow: View {
    var authorInfo: AuthorInfo

    var body: some View{
      NavigationLink(destination: BookRow(authorInfo: authorInfo)){
        Text("\(authorInfo.author.name)")
      }
    }
}

So then our body in ContentView looks like this:

 var body: some View {
      NavigationView {
        List(authorsInfo, id: \.author.id) {authorInfo in
        AuthorRow(authorInfo: authorInfo)
          } .navigationBarTitle(Text("Authors"))
      }
    }
}

In order for this concept to work, the authorsInfo parameter must look like this: [name of author, [books by author]] Hey, that's a fetch in GRDB with associations! So to get an array of every author with each author's books:

let authorsInfo: [AuthorInfo] = try! dbPool.read { db in
 let request = Author
    .including(all: Author.books)
  return try AuthorInfo.fetchAll(db, request) 
}
groue commented 4 years ago

Thank you very much, @danieleprice123 :-) I'll surely refer to your setup when I need it 👍

danieleprice123 commented 4 years ago

I'm sorry to bother again. I can't thank you enough for GRDB and the personal support you provide to your users!

How do you add a var id or id = UUID() to AuthorInfo?

While the above solution works for displaying data, as soon as you try to use @Binding, it falls apart since the List needs a unique index. I was hoping to use the .author.id, but apparently SwiftUI doesn't work like that.

How do you add a var: id or id = UUID() to AuthorInfo? Would I then have to use SuffixRowAdapter? Wouldn't that then mean I have to use .fetchRow and then append an array in a ForEach?

Thanks again! If I can just get a unique id to AuthorInfo, I can get past this and on to the next problem that will cause me to bang my head in the wall...

groue commented 4 years ago

Hi again @danieleprice123. Are you talking about adding conformance to the Identifiable protocol? If so, just add a computed property that fulfills the protocol requirement:

extension AuthorInfo: Identifiable {
    var id: Int64 { author.id }
}